简体   繁体   中英

Put a struct as a generic covariant parameter

I've an Covariant interface, which is in an array of it's parent type. They are defined globally like this:

//The interface/class
public interface IMyType<out T>{}
public class MyType<T>: IMyType<T>{
    T value;
    MyType<T>(T initialValue){
        value = initialValue;
    }
}

I tried this:

IMyType< object>[] tt = new IMyType<object>[]
{
    new MyType<String>( "test"), //Works fine
    new MyType<SomeOtherRandomClass>(new SomeOtherRandomClass()),//works fine
    new MyType<Int32>(12)//Doesn't work
};

Even trying to specify a struct instead of object:

IMyType< struct>[] tt = new IMyType<struct>[]//Doesn't work:Incorrect Number of parameter
{
    new MyType<Int32>(12)//Doesn't work
};

Specifying only a specific struct works:

IMyType< int>[] tt = new IMyType<int>[]//Does work
{
    new MyType<Int32>(12)//Does work
};

So why does this not work? Is there a way to make it work?

It is completely impossible to convert an Interface<ValueType> to Interface<object> as a covariant conversion.

The reason for this is that the JITter must compile a separate version of your class for each value type (since value types have different shapes), so it cannot convert it to any other generic instantiation.

Variance only works for reference types because references are all the same, so the JITted methods can be shared across different type parameters.

Your best solution may be to write a wrapper class that encapsulates a struct:

public class StructContainer<TStruct> where TStruct : struct
{
    TStruct value;
    public StructContainer(TStruct initialValue)
    {
        value = initialValue;
    }
}

Then you could do:

IMyType<object>[] tt = new IMyType<object>[]
{
    new MyType<String>("test"), //Works fine
    new MyType<StructContainer<int>>(
        new StructContainer<int>(12)
    ) // compiles now
};

Granted, this sacrifices the simplicity of your original syntax and forces you to do hokey things (like type checks) later on when you need to pull items out of your collection.

One other option is to track a collection of objects AND a collection of structs as separate collections. This may make more sense because, again, once you pull items out of your list, it's likely that you'll need to treat them differently anyway.

If you want to accomplish putting generic types with different type parameters into the same collection, and you can't use covariance for the reason SLaks mentioned, then you should have the generic type implement a non-generic interface:

public interface IMyType<out T> : ISomeBaseInterface {}

var tt = new ISomeBaseInterface[] 
{
    new MyType<String>( "test"), //Works fine
    new MyType<SomeOtherRandomClass>(new SomeOtherRandomClass()),//works fine
    new MyType<Int32>(12)//now it works
};

This may or may not be sufficient depending on what your other requirements are.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM