In the code below, stepping through the debugger, observe that MyWrapperInstance
, an instance of Child.Wrapper
, has only the hidden field from Base.BaseWrapper
populated as an instance of Child.Node
instantiated via the new()
constructor from the type constraint, so that MyWrapperInstance.Node
still evaluates to null
.
I am somewhat new to C# but this went against my intuition. Is there an explanation for why this works this way?
var MyChildInstance = new Child();
var MyWrapperInstance = MyChildInstance.New();
// evaluates to true:
var isNull = MyWrapperInstance.Node is null;
public class Base
{
public class BaseNode { }
public class BaseWrapper
{
public BaseNode Node;
}
public TWrapper MakeNew<TWrapper, TNode>()
where TWrapper : BaseWrapper, new()
where TNode : BaseNode, new()
{
var wrapper = new TWrapper()
{
Node = null
};
// I expected this to write the derived class's property
// but it seems to write the hidden field of base class
// instead
wrapper.Node = new TNode();
return wrapper;
}
}
public class Child : Base
{
public class Node : BaseNode { }
public class Wrapper : BaseWrapper
{
// Hides BaseWrapper Property
public new Node Node;
}
public Wrapper New() => MakeNew<Wrapper, Node>();
}
Note: I created a new Console application in Visual Studio 17.3 using .NET 6.0 and this is the content of Program.cs.
Expectation: MyWrapperInstance.Node is not null, and the hidden property is null instead. I tried to inspect in the debugger that the instantiated wrapper
has not somehow been cast to BaseWrapper
.
CodePudding user response:
Proper intuition - whenever one uses shadowing (like public new Node Node;
) it is the best to assume to access such property the compiler will choose base or derived version at random. Indeed, there are well defined rules and in every case one can reason about the choice compiler will make, but "random" is a good starting point.
In this case compiler sees TWrapper
as a type that is at least BaseWrapper
but knows nothing about any derived classes. So inside generic code wrapper.Node
will always be BaseWrapper.Node
as compiler must generate all code at compile time and it has no information about essentially unrelated Wrapper.Node
.
Note that in most cases when one tries to use new
modifier virtual
is likely more appropriate - review Difference between shadowing and overriding in C#?.