2

I have the following classes:

public class HeaderBase
{
    public int HeaderSize { get { return sizeof(byte); } }
    public byte[] Content { get; private set; }

    public HeaderBase(byte[] bytes)
    {
        Content = bytes;
    }
}

public class BiggerHeader : HeaderBase
{
    public new int HeaderSize { get { return sizeof(byte) + sizeof(UInt32); } }

    public BiggerHeader(HeaderBase header) : base(header.Content)
    { }     
}

I also have a templated method to marshal and instantiate the BiggerHeader type

public static T Get<T>() where T : HeaderBase
{
    HeaderBase b = new HeaderBase(new byte[]{});
    T instance = (T)Activator.CreateInstance(typeof(T), b);
    return instance;
}

According to MSDN:

where T : <base class name>: The type argument must be or derive from the specified base class.

However, the value of HeaderSize is 1 and not 5 as I would have expected. Why would this be the case, and how can I instantiate an object which will use the new properties from derived types?

DotNetFiddle

Related: Generics in C# - how can I create an instance of a variable type with an argument?

Community
  • 1
  • 1
user5877732
  • 371
  • 3
  • 19
  • 1
    Remove the `new` keyword, the C# compiler now generates a warning. You need to understand that warning, right now you don't. Plenty of good Q+A about it on this site. Using `new` to suppress the warning is a bug. – Hans Passant Aug 03 '16 at 13:38
  • @HansPassant I do understand it. I need the base class to perform logic based on is `HeaderSize` not the inherited size. My question is the instance created by the `Activator` behaving like the base class not the derived type, even though that's type I'm passing as the argument in the call to `CreateInstance` – user5877732 Aug 04 '16 at 08:33
  • It has nothing to do with the instance created by Activator, everything to do with the type of your reference variable. Which is HeaderBase, any non-virtual member is going to act non-virtual. – Hans Passant Aug 04 '16 at 08:42
  • Sorry, not familiar with "reference variable". A quick search only showed stuff about ref params & ref vs value types... Is that `T` or the `b` as the constructor parameter? – user5877732 Aug 04 '16 at 08:58
  • 1
    It is a variable that stores a reference to an object on the GC heap. As opposed to one that stores a value type value. Your `b` variable. All that the compiler knows is that it definitely is compatible with HeaderBase. But it can't know that it might be an object that was derived from HeaderBase. So it can only generate a call to HeaderBase methods and property accessors. If that member is not virtual then you will always get the HeaderBase version. – Hans Passant Aug 04 '16 at 09:07
  • Aha! That makes perfect sense now. Many thanks! :) – user5877732 Aug 04 '16 at 09:11

2 Answers2

3

new members have the same name as a base member but are otherwise unrelated. It looks like you want to make use of virtual in the base and override in the derived class.

With new you essentially silenced the warning that warned you about this. new had no functional effect.

Calls on T are resolved as if T was HeaderBase. Anything else would require the runtime to perform a dynamic binding at runtime based on the name of what you called. Imagine T t; t.Xyz();. That code would not compile because no Xyz was found statically. But you are doing the same thing! At the time of compiling the method there is no Derived.HeaderSize visible because we don't know that T is going to be Derived. It could end up being something else. That's why the call is statically bound to Base.HS. The fact that B.HS and D.HS have the same name means nothing. It's a coincidence.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I understand what `new` and `override` mean. The question is why does the call to `Activator.CreateInstance` seemingly creates an instance of `BaseHeader` instead of the derived class, which is the argument passed to the function call – user5877732 Aug 04 '16 at 08:36
  • It creates a derived instance but the variable is typed as T which is of effective type HeaderBase. So if you say instance.HeaderSize you're calling the base method. It's just as if you said ((Base)instance).HeaderSize. That clearly calls the base method. – usr Aug 04 '16 at 08:45
  • *the variable is typed as T which is of effective type HeaderBase* but **why** is that the case? I'm passing the type that I want to `CreateInstance`; where is it getting that I want anything to do with the base class? The method constrain specifically says that it allows derived types... what am I missing? – user5877732 Aug 04 '16 at 08:52
  • What does `HeaderBase b = new Derived(); b.HeaderSize` evaluate to in your mind? It calls Base.HeaderSize. This does not have anything to do with Activator. The type of the variable causes this. – usr Aug 04 '16 at 08:56
  • I understand that, but I don't see in my example where I am assigning a derived type to a base variable. I'm not trying to be deliberately obtuse, btw; I genuinely don't understand. I appreciate your help. – user5877732 Aug 04 '16 at 09:01
  • Calls on T are resolved *as if* T was HeaderBase. Anything else would require the runtime to perform a dynamic binding at runtime based on the name of what you called. Imagine `T t; t.Xyz();`. That code would not compile because no Xyz was found statically. But you are doing the same thing! At the time of compiling the method there is no Derived.HeaderSize visible because we don't know that T is going to be Derived. It could end up being something else. That's why the call is statically bound to Base.HS. The fact that B.HS and D.HS have the same name means nothing. It's a coincidence. – usr Aug 04 '16 at 09:06
  • Many thanks for the extended explanation! You can add that to the answer and I'll go ahead and accept it – user5877732 Aug 04 '16 at 09:12
1

Well, I believe that mainly error here is a result of bad architecture.

Let's add some improvements and make all properties, that has to be changed in every other derived class - abstract. By doing so we'll make sure that we didn't forget anything, and can start using polymorphism (override behaviour).

Let's also use some features of C# 6.0

It'll also make code more readable :

public abstract class AbstractHeader
{
    public abstract int HeaderSize { get; }
    public virtual byte[] Content { get; set; }

    protected AbstractHeader() { }

    protected AbstractHeader(byte[] bytes)
    {
        Content = bytes;
    }
}

public class BaseHeader : AbstractHeader
{
    public override int HeaderSize => sizeof (byte);
}

public class BiggerHeader : AbstractHeader
{
    public override int HeaderSize => sizeof (byte) + sizeof (UInt32);

    public BiggerHeader(BaseHeader header) : base(header.Content)
    {
    }
}
Fabjan
  • 13,506
  • 4
  • 25
  • 52