无法转换通用对象

时间:2015-04-16 11:36:28

标签: c# generics covariance

我有以下概念模型:

public interface IFoo<out T>
{
    T Data { get; }
}

public struct Foo<T> : IFoo<T>
{
    public Foo(T data) : this()
    {
        Data = data;
    }

    public T Data { get; private set; }
}

public class FooService<T> 
{
    ...

    public Foo<T> Get(string id)
    {
        ...
    }
}

然后我尝试以一种概念上等同于此的方式使用它:

// Create and register a few FooService instances
ServiceLocator.Register(new FooService<DateTime>(), "someServiceId");
ServiceLocator.Register(new FooService<double?>(), "anotherServiceId");

// Retrieve a particular FooService instance and call the Get method
var fooService = (FooService<object>)ServiceLocator.Get("someServiceId");
var foo = fooService.Get("someFooId");

我想在FooService实例上使用Get()方法 - 无论选择的FooService实例返回什么类型。但是,此代码会导致以下异常:

  

无法将“WindowsFormsApplication7.FooService`1 [System.DateTime]”类型的对象强制转换为“WindowsFormsApplication7.FooService`1 [System.Object]”。

有关如何解决此问题的任何建议都将受到高度赞赏。

你可能会争辩,为什么我首先制作了FooService泛型。但是,这样做是为了在类型安全的环境中确保类型安全。但是,在这种特殊情况下,FooService应该在Web API控制器中用于服务各种类型的Foo。无论T的类型如何,它都应返回T的Foo响应。

3 个答案:

答案 0 :(得分:7)

我认为你已经给出了正确答案:

FooService<double?>永远不能投放到FooService<object>,而且 永远不能将FooService<DateTime>投放到FooService<object>

使用协方差或逆变不会改变这一点。页面https://msdn.microsoft.com/en-us/library/dd997386.aspx说明:Value types also do not support variance.由于double?DateTime是值类型,因此无法使用。

答案 1 :(得分:1)

如果可能,您可以像这样调整模型:

public interface IFoo
{
    object Data { get; }
}

public interface IFoo<T> : IFoo
{
    new T Data { get; }
}

public class Foo<T> : IFoo<T>
{
    public Foo(T data) { Data = data; }
    public T Data { get; private set; }
    object IFoo.Data { get { return Data; } }
}

public interface IFooService
{
    IFoo Get(string id);
}

public interface IFooService<T> : IFooService
{
    new IFoo<T> Get(string id);
}

public class FooService<T> : IFooService<T>
{
    public IFoo<T> Get(string id) { return null; }
    IFoo IFooService.Get(string id) { return Get(id); }
}

这将使您能够

ServiceLocator.Register(new FooService<DateTime>(), "someServiceId");
ServiceLocator.Register(new FooService<double?>(), "anotherServiceId");

// Retrieve a particular FooService instance and call the Get method
IFooService fooService = (IFooService)ServiceLocator.Get("someServiceId");
IFoo foo = fooService.Get("someFooId");
object data = foo.Data;

答案 2 :(得分:0)

我做了一个实际适用于引用类型的示例,但是 - 正如@Martin所述 - 永远不能用于值类型,因为它们不支持方差。 where T : class约束确保只接受引用类型。

public interface IFoo<out T> where T : class
{
     T Data { get; }
}

public struct Foo<T> : IFoo<T> where T : class
{
    public Foo(T data) : this()
    {
        Data = data;
    }

    public T Data { get; private set; }
}

public interface IFooService<out T> where T : class 
{
    IFoo<T> Get(string id);
}

public class FooService<T> : IFooService<T> where T : class
{
    public IFoo<T> Get(string id)
    {
        return null;
    }
}

用法:

ServiceLocator.Register(new FooService<Bar>(), "ServiceId");
// OK because Bar is a reference type 
var fooService = (IFooService<object>)ServiceLocator.Get("ServiceId"); 
var foo = fooService.Get("FooId");