I had the chance to dig deeper into how C# handles members in general and came up with some really interesting conclusions. This post is mostly about how C# and the CLR resolves fields and methods calls with the corresponding implications. This is especially good to shut up someone if you do an interview with a know-it-all interviewer..
Actually is it pretty interesting because in C# you can actually have multiple fields with the same name in an object. But there is a small gotcha: you can't declare the duplicate fields in the same class definition. So how can you do this? Well you use inheritance. You can have a simple class that inherits from another base class declaring another field with the exact same name as field from the base class.
They will look like this:
In this case, what field will be accessed from these duplicate fields? It's actually pretty simple. It depends on how you reference the object. Each object has a type which can be a class, structure or something else but also each reference has a type. When a field is being accessed, it checks the reference on the object being accessed and gets the field from it's type if possible. So if we refer our object through a reference to the base class, then it will get the field from the base class. If we reference the object through a reference with the derived type then we will access the field from the derived type.
This actually means that each field belongs to a type and when we have duplicate fields it gets the field that belongs to the type of the object's reference.
Now the funny thing is that the same principle is also applied to methods. We can have a derived type and a base type each with a method having the same name and signature without overriding and virtual options like in the example bellow:
In this case when we can think of the method calling process in 2 steps. First figure out what method to call and after that call it. The first part where the method resolution is done is exactly the same as field resolution mentioned before.
Now this poses a problem. Sometimes we need to always call the method from the derived type and that's why method overriding was introduced into object oriented languages like C#. If we add the keyword virtual to the method in the base class and the keyword override to the method in the derived class then the method from the derived class will always be chosen no matter how we reference the object. It's really important to add the keyword override to the method in the derived class otherwise this mechanism won't work and the derived method won't get invoked from base class references.
Our modified classes classes will look like this:
In that example there are actually 2 derived classes. Only the method from the first derived class with the override keyword gets called.
But we can also have a combination of virtual, non virtual and overriding methods. We can have multiple virtual/override chains on an inheritance tree. So we can have 4 or more classes that are derived one from another forming a chain like the one bellow:
What happens when we try to call some methods like that? It depends. As a general rule, the runtime looks at the method in the reference type. If it's non-virtual it will chose it and call it directly. If it's virtual then it looks at the method from the first derived type leading down the inheritance chain to the actual object type. If it has the override keyword then it will move on to it else it will stop calling the method from the reference type. If it has the override keyword it looks again at the first derived type in the inheritance chain. Does it have the override keyword again? Then repeat the process and chose it instead. Now check again in the derived type and so on. This will stop when it reaches the object's type that we call the method on or until it finds a method without the override keyword.
All in all, there is a simple rule when calling a method on an object. Is it non-virtual? Then call the method from the object's reference type. Is it virtual? Then find all the derived type between the object reference type and the actual object type. You will have a chain with the top being the reference type and bottom being the object type. After that just jump down from the reference type into the methods from the derived types one by one until you run into a method without the override keyword and call the method before that.
In the big example bellow if we reference an object from InheritanceChainDemoClassLevel1 or InheritanceChainDemoClassLevel2 types and call the method then the one from InheritanceChainDemoClassLevel3 will get called.
On the other hand if we reference it by reference of type InheritanceChainDemoClassLevel4 and call the method on the object then the one from InheritanceChainDemoClassLevel5 will get called.
With methods we can also do something special with them. Capture them with a delegate. In school or in manuals it generally states that a delegate retains the object and the method that we want to call on that object. So when we call that method we reproduce the exact call that would have happened if we called it in the exact same context as when we assigned a method to the delegate.
But remember the first case with methods in which we had duplicate methods. In this case, when we assign it to a delegate, it needs to figure out what method to call exactly when we invoke the delegate. I don't know exactly but there are 2 approaches to resolve this problem. Look up the proper method before assigning it to the delegate.
And the other approach is to include the object's reference type in the delegate. This way when we invoke it, it will know how to look up the proper method to call.
What happens now if we assign to a delegate a virtual method? It actually has to include the reference type inside the delegate to know what method to call. Or to resolve the method before assigning it to the delegate.
That about all. There is are still a some things left behind like interfaces, abstract classes or generic methods or even virtual generic methods. But those are covered a lot more on the internet than the things I mentioned in this post.
I posted the code used in this post on my GitHub account at this address: https://github.com/Alecu100/MethodsDelegatesFieldsDemo
With all of these you have all it takes to make fun of a cocky interviewer if you really want to and put him back into his place.
Actually is it pretty interesting because in C# you can actually have multiple fields with the same name in an object. But there is a small gotcha: you can't declare the duplicate fields in the same class definition. So how can you do this? Well you use inheritance. You can have a simple class that inherits from another base class declaring another field with the exact same name as field from the base class.
They will look like this:
In this case, what field will be accessed from these duplicate fields? It's actually pretty simple. It depends on how you reference the object. Each object has a type which can be a class, structure or something else but also each reference has a type. When a field is being accessed, it checks the reference on the object being accessed and gets the field from it's type if possible. So if we refer our object through a reference to the base class, then it will get the field from the base class. If we reference the object through a reference with the derived type then we will access the field from the derived type.
This actually means that each field belongs to a type and when we have duplicate fields it gets the field that belongs to the type of the object's reference.
Now the funny thing is that the same principle is also applied to methods. We can have a derived type and a base type each with a method having the same name and signature without overriding and virtual options like in the example bellow:
In this case when we can think of the method calling process in 2 steps. First figure out what method to call and after that call it. The first part where the method resolution is done is exactly the same as field resolution mentioned before.
Now this poses a problem. Sometimes we need to always call the method from the derived type and that's why method overriding was introduced into object oriented languages like C#. If we add the keyword virtual to the method in the base class and the keyword override to the method in the derived class then the method from the derived class will always be chosen no matter how we reference the object. It's really important to add the keyword override to the method in the derived class otherwise this mechanism won't work and the derived method won't get invoked from base class references.
Our modified classes classes will look like this:
In that example there are actually 2 derived classes. Only the method from the first derived class with the override keyword gets called.
But we can also have a combination of virtual, non virtual and overriding methods. We can have multiple virtual/override chains on an inheritance tree. So we can have 4 or more classes that are derived one from another forming a chain like the one bellow:
What happens when we try to call some methods like that? It depends. As a general rule, the runtime looks at the method in the reference type. If it's non-virtual it will chose it and call it directly. If it's virtual then it looks at the method from the first derived type leading down the inheritance chain to the actual object type. If it has the override keyword then it will move on to it else it will stop calling the method from the reference type. If it has the override keyword it looks again at the first derived type in the inheritance chain. Does it have the override keyword again? Then repeat the process and chose it instead. Now check again in the derived type and so on. This will stop when it reaches the object's type that we call the method on or until it finds a method without the override keyword.
All in all, there is a simple rule when calling a method on an object. Is it non-virtual? Then call the method from the object's reference type. Is it virtual? Then find all the derived type between the object reference type and the actual object type. You will have a chain with the top being the reference type and bottom being the object type. After that just jump down from the reference type into the methods from the derived types one by one until you run into a method without the override keyword and call the method before that.
In the big example bellow if we reference an object from InheritanceChainDemoClassLevel1 or InheritanceChainDemoClassLevel2 types and call the method then the one from InheritanceChainDemoClassLevel3 will get called.
On the other hand if we reference it by reference of type InheritanceChainDemoClassLevel4 and call the method on the object then the one from InheritanceChainDemoClassLevel5 will get called.
With methods we can also do something special with them. Capture them with a delegate. In school or in manuals it generally states that a delegate retains the object and the method that we want to call on that object. So when we call that method we reproduce the exact call that would have happened if we called it in the exact same context as when we assigned a method to the delegate.
But remember the first case with methods in which we had duplicate methods. In this case, when we assign it to a delegate, it needs to figure out what method to call exactly when we invoke the delegate. I don't know exactly but there are 2 approaches to resolve this problem. Look up the proper method before assigning it to the delegate.
And the other approach is to include the object's reference type in the delegate. This way when we invoke it, it will know how to look up the proper method to call.
What happens now if we assign to a delegate a virtual method? It actually has to include the reference type inside the delegate to know what method to call. Or to resolve the method before assigning it to the delegate.
That about all. There is are still a some things left behind like interfaces, abstract classes or generic methods or even virtual generic methods. But those are covered a lot more on the internet than the things I mentioned in this post.
I posted the code used in this post on my GitHub account at this address: https://github.com/Alecu100/MethodsDelegatesFieldsDemo
With all of these you have all it takes to make fun of a cocky interviewer if you really want to and put him back into his place.
Comments
Post a Comment