⬆️ ⬇️

Logical but illegal

I believe that many who came to the glorious world of .NET from the glorious world of C ++ remember very well how they had to literally dig into the standard to figure out why a language behaves this way and not otherwise. Many things that seemed completely obvious to them, on closer examination, turned out to be not something that was not obvious - but just the opposite of common sense, which we all used to rely on.



However, most likely, this is a problem in many programming languages. Many, I think, remember the well-known WAT video on the problems of some of the "obvious" languages ​​of JavaScript and Ruby. The logic of the familiar world comes out to smoke when border areas appear - those in which normal people do not climb.



However, I suggest a little distraction from these lofty matters and a look at C # language from a slightly different side. Namely, to look at some constructions that, on the one hand, are completely understandable and easily described in terms of a language, and on the other, completely refuse to compile.

')

I'm not even going to argue with whether they should compile and work. Once again, I’ll just remind you once again that everything that is "logical and understandable" for us can actually be completely illogical and incomprehensible.



And yes, I will not once again whimp at the topic of what I would like to return from the function stupid without using cumbersome structures and get the name of a variable using a simple operator. This is not here - this is to Microsoft in feature requests. We have five minutes of humor. So, all that you want to write every day, but do not write, because you know - it will not compile. Go!



Let's start with the simplest.



int i = 0; 




What do i hear How "what does this code do"? Get out of the profession! Such an intellectual majority has no place in our egghead club. This code does nothing - the compiler will throw it out when optimizing and will be completely right. But the meaning of this record is clear even to the mentally retarded - we define a variable with the name i and assign a value of 0 to it.



Let us turn to a more complex example.



 int i = 0; int j = i; 




Another half of the room left our coven, unable to understand the amazing complexity of the examples. Do not close the tab - I theatrically aggravate the situation before the climax.



Let's try this

 int i = 0, j = 0; 


So

 int i = 0, j = i; 


No problem at all.



But as soon as we want to go some math, the compiler immediately appears from non-existence and gives us a meter-long rump. Do not dare, they say, clutter up my code with equations!



 int i = j = 0; // error CS0103: The name `j' does not exist in the current context 




But the Russians do not give up!



 int i = int j = 0; // error CS1525: Unexpected symbol `j', expecting `.' 




Now, when the weak in spirit have left our circle "Skilful hands", it is possible to somewhat diversify the discussion with the help of a ternary operator!



 int i = whatever ? 1 : 2; 




We write such code every day. But not everyone knows (especially those who never wrote in C ++, however, as well as those who consider the semantics of this language several times more powerful than C # and have no idea that many operations are done there in a similar way), can be done like this.



 int i = whatever ? (j = 1) : 2; 




I doubt that there are people for whom this record is a mystery. It means a simple thing - if whatever, then assign j to one and assign the result of the assignment (that is, the value j) to variable i. Otherwise, assign it to her 2. Armed with this incredible power, shoot from the hip!



 int i = whatever ? 2 * (j = 1) - 1 : 2; 




But one always wants a strange one — for example, to solve the bicubic equation in the ternary operator. Or just do something left.



 int i = whatever ? { j = 1; return 2; } : 2; // error CS8025: Parsing error 




Alas, the human mind is a fiasco in the face of a bloodthirsty machine. He is trying to find a workaround ...



 int i = whatever ? ( () => 1 )() : 2; // error CS0119: Expression denotes a `anonymous method', where a `method group' was expected 




So I hear the compiler scream:

- Yes, I will make you a delegate, I will, you just tell me what type?

- Anyone!

- What?

- Anyone. Anyone. Anyone!

- System.MulticastDelegate.

- No, well, not any of course ...



(by the way, who will help you find this video on YouTube - I will be very grateful)



And finds it!



 int i = whatever ? new System.Func<int>( () => 1 )() : 2; 




Although, alas, it is not as beautiful as the original idea.



 int i = whatever ? new System.Func<int>( () => { j = 1; return 2 + j; } )() : 2; 




But on bezrybe, as they say, and a boot - a sardine.



Now, when half of the remaining people are already snoring, you can go to the complex instructions - blocks.



We all wrote cycles.



 for(int i = 0; i < 10; i++); 




And we know that in fact this record is similar to the following:



 { int i = 0; while(i < 10) i++; } 




Well, such



 int i; for(i = 0; i < 10; i++) 




Similar to this code



 int i = 0; while(i < 10) i++; 




In general, it is great to declare variables for control instructions directly in the instructions themselves, and not to allocate a special block for this. Otherwise many compact things would have to be written much more complicated. Especially it concerns the block using



 using(IDisposable data = GetSomeData()) { } 




Which unfolds into something like that



 { IDisposable data = null; try { data = GetSomeData(); } finally { if(data != null) data.Dispose(); } } 




Now imagine that we want to write the following expression in abbreviated form



 { int i = 5; if(i < 10) i = 7; } 




It would be logical to declare a variable in a control instruction. Victory?



 if((int i = 5) < 10) // error CS1525: Unexpected symbol `i', expecting `)' or `.' i = 7; 




So close ...



Okay, but C # is object oriented. Let's play with objects!



Recall how we simply and efficiently called the parent constructor from the constructor of the derived class. What an elegant and clear syntax was there - a sight for sore eyes!



 class SomeClass { public SomeClass(int data) { } } class SomeOtherClass : SomeClass { public SomeOtherClass(int data) : base (data) { } } 




And I immediately want a strange one.



 class SomeClass { public virtual void SomeMethod(int data) { } } class SomeOtherClass : SomeClass { public override void SomeMethod(int data) : base (data) { } // Unexpected symbol `:' in class, struct, or interface member declaration } 




The last time I tried to convince the compiler that my record form made sense, it broke two of my ribs. Be careful with experiments!



And now, when only one person reads me (yes, it is you - please don't leave - I'm afraid of loneliness) we'll see another example, which is not something that you want, but not it can, but just so strange that it baffles even me.



Unlike the others, it, by the way, is compiled in all variants.



We will do everything that we do every day, but in a somewhat unusual way. Of the sub-sub, so to speak.



We will have a hierarchy of two classes that implement one interface. And we will make the following calls and look at the results.



 new FooA().Foo(); new FooB().Foo(); ((IFoo) new FooA()).Foo(); ((IFoo) new FooB()).Foo(); ((FooA) new FooB()).Foo(); ((IFoo)(FooA) new FooB()).Foo(); 




Because here we are so funny.



Let's start with the simplest - virtual methods. Like a book.



 interface IFoo { void Foo(); } class FooA : IFoo { public virtual void Foo() { System.Console.WriteLine("A"); } } class FooB : FooA, IFoo { public override void Foo() { System.Console.WriteLine("B"); } } 




The result is obvious!



 new FooA().Foo(); // A new FooB().Foo(); // B ((IFoo) new FooA()).Foo(); // A ((IFoo) new FooB()).Foo(); // B ((FooA) new FooB()).Foo(); // B ((IFoo)(FooA) new FooB()).Foo(); // B 




But we are smart. Why do we need virtual methods? We have an interface.



 interface IFoo { void Foo(); } class FooA : IFoo { public void Foo() { System.Console.WriteLine("A"); } } class FooB : FooA, IFoo { public void Foo() { System.Console.WriteLine("B"); } } 




Of course we will get a warning



 // warning CS0108: `FooB.Foo()' hides inherited member `FooA.Foo()'. Use the new keyword if hiding was intended 




But do not pay attention - the problem is not in it, and the new keyword does not affect the experiment at all.



The result is staggering.



 new FooA().Foo(); // A new FooB().Foo(); // B ((IFoo) new FooA()).Foo(); // A ((IFoo) new FooB()).Foo(); // B ((FooA) new FooB()).Foo(); // A ((IFoo)(FooA) new FooB()).Foo(); // B 




The most monstrous here is the difference between the method call through the cast to the base class and the method call through the cast to the base class, and then to the interface. Because the method behaves as a virtual method would behave with the exception of one case - reduction to the base class.



Let's try to emulate it without interfaces. Assume that the interface will always call the first class method in the hierarchy.



 class IFoo { public void Foo() { System.Console.WriteLine("A (I)"); } } class FooA : IFoo { public void Foo() { System.Console.WriteLine("A"); } } class FooB : FooA { public void Foo() { System.Console.WriteLine("B"); } } 




Alas, a fiasco - the behavior is absolutely not what we want.



 new FooA().Foo(); // A new FooB().Foo(); // B ((IFoo) new FooA()).Foo(); // A (I) ((IFoo) new FooB()).Foo(); // A (I) ((FooA) new FooB()).Foo(); // A ((IFoo)(FooA) new FooB()).Foo(); // A (I) 




If it calls the last method in the hierarchy, the behavior is also drastically different from what we have with the interface.



No logic can comprehend exactly this behavior. Unless paragraph 3 of subparagraph 14 of paragraph 1 of chapter 7 of part 3 of volume 2 of the standard of 2005 revision 4, adopted in 1 reading of the 2nd meeting in the third week of the fourth month.



Unfortunately, I have to leave reflections on the reasons for this for self-reflection - my compiler, unfortunately, was too vigorous about Friday and they are now snoring in the kitchen with the standard. Perhaps I will do the same. Have a nice dream and a good weekend!

Source: https://habr.com/ru/post/151964/



All Articles