kalashn1k0v
kalashn1k0v

Reputation: 43

How to use generic parameter type for another generic class?

For me it looks like a compiler bug or some strange behavior. Compiler can't determine generic parameter type in generic class

Code

public interface IHamster
{
    int Some { get; set; }
}

public abstract class BaseHamster : IHamster
{
    public int Some { get; set; }
}

public class DerivedHamster : BaseHamster
{
}

class ApplyHitHamster<T> where T : IHamster   // <-- same constraint 
{
    void Zu()
    {
        BaseHamster hamster = null;
        var derived = new DerivedHamster();
        IHamster i = derived;

        var s = new TakeDamageHamster<T>(i); // <<<< Compilation Error on any variables(hamster,derived,i) WHY?????????
        var s2 = new TakeDamageHamster<IHamster>(i); // <<<< But THIS works well
    }
}

class TakeDamageHamster<T> where T : IHamster   // <-- same constraint 
{
    public TakeDamageHamster(T Hamster)
    {
        Console.WriteLine(Hamster.Some);
    }
}

How can it be possible to use <T> with same where constraint instead of the <IHamster> direct constraint?

Why can't the compiler determine type, if both classes have the same where T : IHamster constraint?

EDIT: Another simplified example:

public class BaseHamster
{
    public int Some { get; set; }
}

public class DerivedHamster : BaseHamster
{
}

class ApplyHitHamster<T> where T : BaseHamster, new()   // <-- same constraint 
{
    void Zu()
    {
        BaseHamster hamster = new BaseHamster();
        var derived = new DerivedHamster();

        var s = new TakeDamageHamster<T>();
        s.Method(hamster); // <<<< Compilation Error on any variables(hamster,derived) WHY?????????
    }
}

class TakeDamageHamster<T> where T : BaseHamster, new()  // <-- same constraint 
{
    public void Method(T hamster)
    {
        Console.WriteLine(hamster.Some);
    }
}

Another example:

public class BaseHamster
{
    public int Some { get; set; }
}

class ApplyHitHamster<T> where T : BaseHamster, new()   // MSDN: 
{
    void Zu()
    {
        var hamster = new BaseHamster();
        SuperMethod(hamster);  // <<<< WTF? T is ALWAYS BaseHamster!!!
        SuperMethod(hamster as T);    
    }
    void SuperMethod(T x)
    {
    }
}

Upvotes: 4

Views: 675

Answers (4)

Agent_L
Agent_L

Reputation: 5420

It works OK. Imagine you have ApplyHitHamster<DerivedHamster>. Now, content of Zu unwinds to :

IHamster i = derived;

var s = new TakeDamageHamster<DerivedHamster>(i);

You can clearly see that ctor of TakeDamageHamster requires DerivedHamster and you're trying to pass bare IHamster to it. It cannot work.

if you want your Zu to work as it is, you need to do:

class TakeDamageHamster<T> where T : IHamster   // <-- same constraint 
{
    public TakeDamageHamster(IHamster Hamster)//<-- now this is compatible with constraint alone, no matter what T is.
    {
        Console.WriteLine(Hamster.Some);
    }
}

Now, you can create TakeDamageHamster with any generic parameter you want and initialize it with only base interface.

Upvotes: 0

smoksnes
smoksnes

Reputation: 10871

How to make it work?

1. What you can do to make it work is to cast it to T.

BaseHamster hamster = null;
var derived = new DerivedHamster();
T i = derived as T;
var s = new TakeDamageHamster<T>(i);

But then you also need to add the class constraint.

class ApplyHitHamster<T> where T : class, IHamster
{
    // Other stuff..
}

2. Alternatively you can change the constructor to use the interface instead. That will also work.

class TakeDamageHamster<T> where T : IHamster
{
    public TakeDamageHamster(IHamster Hamster)
    {
        Console.WriteLine(Hamster.Some);
    }
}

3. Or you can use new T(). Remember that this also requires you to add the new() constraint.

BaseHamster hamster = null;
var derived = new T();

var s = new TakeDamageHamster<T>(derived); // <<<< Compilation Error on any variables(hamster,derived,i) WHY?????????
var s2 = new TakeDamageHamster<IHamster>(derived); // <<<< But THIS works well

Why it doesn't work?

Because the constraint does not guarantee that i is actually derived from T. Let's say we create a AnotherHamster. Note that it inherits from BaseHamster, but not from DerivedHamster.

public class DerivedHamster : BaseHamster
{
}

public class AnotherHamster : BaseHamster
{
}

And now we create an instance of ApplyHitHamster.

var fooHamster = new ApplyHitHamster<AnotherHamster>();
fooHamster.Zu(); // Let's pretend that the method is public. :)

This will in the end try to create an instance of TakeDamageHamster<AnotherHamster>. But wait, you're trying to send a DerivedHamster to it's constructor.

BaseHamster hamster = null;
var derived = new DerivedHamster();
IHamster i = derived;
// You cannot send DerivedHamster when it expects AnotherHamster.
var s = new TakeDamageHamster<T>(i); // T is now AnotherHamster.

Remember that i is a DerivedHamster, but TakeDamageHamster<AnotherHamster> expects a AnotherHamster. Therefore it does not compile.

Another example. Let's say that you initialize your class like so:

var fooHamster = new ApplyHitHamster<BaseHamster>();
fooHamster.Zu();

Now T is BaseHamster. Which would make the code look something like this:

var derived = new DerivedHamster();
IHamster i = derived;
var s = new TakeDamageHamster<BaseHamster>(i); // Cannot pass IHamster when ctor expects BaseHamster.

It will not compile because TakeDamageHamster expects a BaseHamster (or something that derives from it) as a parameter for it's constructor. But you're sending it a IHamster. They are not the same things, even though BaseHamster implements IHamster. IHamster does not derive from BaseHamster.

There might be several other implementations of IHamster, which does not derive from BaseHamster. And your code shouldn't break just because another implementation of IHamster is created, right? So the compiler does not allow that, simply because your constaint does not limit this.

Upvotes: 6

Paul Tsai
Paul Tsai

Reputation: 903

The issue is with this line.

var s = new TakeDamageHamster<T>(i); 

The reason why this is throwing an error is because there is no guarantee that T will be of type DerivedHamster. That is, T is guaranteed to be of type IHamster only. Recommended would be using the following line.

var s2 = new TakeDamageHamster<DerivedHamster>(derived); 

Also consider using a helper method to make the code a bit easier to read.

class ApplyHitHamster<T> where T : IHamster  
{
    void Zu()
    {
        var derived = new DerivedHamster();
        var s2 = new TakeDamageHamster<DerivedHamster>(derived); 
        var s3 = CreateTakeDamageHamster(derived);
    }

    TakeDamageHamster<T2> CreateTakeDamageHamster<T2>(T2 hammie)
        where T2 : IHamster 
    {
        return new TakeDamageHamster<T2>(hammie);
    }
}

Upvotes: 0

Artur Hovhannisyan
Artur Hovhannisyan

Reputation: 104

When Passing T you need constructor like this beacuse your T can only be type of IHamster

public TakeDamageHamster(IHamster i)
        {
            // TODO: Complete member initialization
            this.i = i;
        }

public interface IHamster { int Some { get; set; } }

    public abstract class BaseHamster : IHamster
    {
        public int Some { get; set; }
    }

    public class DerivedHamster : BaseHamster
    {
    }

    class ApplyHitHamster<T> where T : IHamster   // <-- same constraint 
    {
        void Zu()
        {
            BaseHamster hamster = null;
            var derived = new DerivedHamster();
            IHamster i = derived;

            var s = new TakeDamageHamster<T>(i); // <<<< Compilation Error on any variables(hamster,derived,i) WHY?????????
            var s2 = new TakeDamageHamster<IHamster>(i); // <<<< But THIS works well
        }
    }

    class TakeDamageHamster<T> where T : IHamster   // <-- same constraint 
    {
        private IHamster i;

        public TakeDamageHamster(T Hamster)
        {
            Console.WriteLine(Hamster.Some);
        }

        public TakeDamageHamster(IHamster i)
        {
            // TODO: Complete member initialization
            this.i = i;
        }
    }

Upvotes: 0

Related Questions