Casting a generic element type downwards

会有一股神秘感。 提交于 2019-12-23 09:25:51

问题


public class ConfigControlBase<T> : UserControl
    where T : ProviderBase
{
    public T Provider { get; set; }

    public void Init(T provider)
    {
        this.Provider = provider;
    }
}


public abstract class ProviderBase
{
    public abstract ConfigControlBase<ProviderBase> GetControl();
}

public class ProviderXConfigControl : ConfigControlBase<ProviderX>
{
}

public class ProviderX : ProviderBase
{
    public override ConfigControlBase<ProviderBase> GetControl()
    {
        var confControl = new ProviderXConfigControl() as ConfigControlBase<ProviderX>;
        return confControl;
    }
}

return confControl; throws an exception:

Cannot implicitly convert type ConfigControlBase<ProviderX> to ConfigControlBase<ProviderBase>


回答1:


Let's change the name of your classes and properties, but keep the shape the same:

public class Cage<T> where T : Animal
{
    public T Contents { get; set; }
}

public class Aquarium : Cage<Fish> { }

public abstract class Animal
{
    public abstract Cage<Animal> GetCage();
}

public class Fish : Animal
{
    public override Cage<Animal> GetCage()
    {
        return (Cage<Animal>)(new Aquarium());
    }
}

Now is it clear why this is not legal? Suppose it were legal. Then you could do this:

Fish fish = new Fish();
Cage<Animal> cage = fish.GetCage();
cage.contents = new Tiger();

And now you have a tiger in your aquarium. And no one wants that.

The compiler (or runtime) has to prevent this type error somehow; it chooses to prevent it as soon as possible. The earliest it can do so is on the type test for the conversion from Aquarium to Cage<Animal>. The compiler knows that this can eventually lead to tigers in aquariums, so it does not allow the conversion at all. If you force the compiler to allow it through casts then it fails at runtime.




回答2:


Generic types with assignable type arguments are not assignable themselves.
For instance, you cannot cast List<string> to List<object>, although string is an object.

It is not immediately obvious why such casting is not supported so let me give you an example:

var words = new List<string> { "Serve God", "love me", "mend" };
var objects = (List<object>) words; // C# compiler wouldn't allow this
objects.Add (new Car()); // we just added a Car to Shakespeare's work and the universe exploded

C# doesn't encourage universe explosion, however since C# 4.0 a light version of this idea is implemented. You see, in some cases such casting would actually be safe.

.NET 4.0 brings concepts of covariance and contravariance in generics only for interfaces and delegates, you may want to check this out.

Example (doesn't work prior to .NET 4.0):

void HandleCollection (IEnumerable<object> collection)
{
    // ...
}

var words = new List<string> { "Serve God", "love me", "mend" };

// IEnumerable is defined as IEnumerable<out T> in .NET 4.0
// 'out' keyword guarantees that T is only used for return values
// and therefore client code can't explode the universe   

var objects = (IEnumerable<object>) words;
HandleCollection (objects);



回答3:


This is because ConfigControlBase<ProviderX> is not a ConfigControlBase<ProviderBase>




回答4:


your

 public override ConfigControlBase<ProviderBase> GetControl()

doesn't match

var confControl = new ProviderXConfigControl() as ConfigControlBase<ProviderX>;



回答5:


This answer might not be useful in your scenario, as you should probably look for another solution, but during reflection I found the ability to cast to less generic types very useful, hence I wrote a solution for it. It only works for interfaces however, and you do have to guarantee you will only pass objects of the correct types to the interface.

I basically generate a proxy class at runtime which does all the required casts for you. It's usage looks as follows:

object validator;  // An object known to implement IValidation<T>.
object toValidate; // The object which can be validated by using the validator.

// Assume validator is IValidation<string> and toValidate a string.

IValidation<object> validation
    = Proxy.CreateGenericInterfaceWrapper<IValidation<object>>( validator );

validation.IsValid( toValidate ); // This works! No need to know about the type.

// The following will throw an InvalidCastException.
//validation.IsValid( 10 );

More information and source code can be found on my blog.



来源:https://stackoverflow.com/questions/4923936/casting-a-generic-element-type-downwards

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!