【问题标题】:Unable to cast generic object无法投射通用对象
【发布时间】:2015-04-16 11:36:28
【问题描述】:

我有以下概念模型:

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 的响应。

【问题讨论】:

  • 你还需要一个接口 IFooServive,你的演员表是 (IFooService),你的 FooService 可以是 IFooService,但正如下面指出的那样,这不起作用对于值类型。

标签: c# generics covariance


【解决方案1】:

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

FooService&lt;double?&gt; 永远不能转换为 FooService&lt;object&gt;,并且 FooService&lt;DateTime&gt; 永远不能转换为 FooService&lt;object&gt;

使用协变或逆变不会改变这一点。 https://msdn.microsoft.com/en-us/library/dd997386.aspx 页面声明:Value types also do not support variance. 并且由于 double?DateTime 是值类型,所以这永远不会起作用。

【讨论】:

  • 双倍?不是值类型。是 Nullable,他也可以使用 DateTime?而不是日期时间。
  • @WojciechKulik, Nullable&lt;T&gt;struct
  • @haim770 哦...是的,你是对的,我的错误:-)。无论如何,这可能会有所帮助:Creating Variant Generic Interfaces
  • @haim770 谢谢,我明白了。但是对于实现我的目标的解决方法有什么建议吗? “我想在 FooService 实例上使用 Get() 方法——无论所选的 FooService 实例返回什么类型”
  • @Lars:解决方案必须是通用解决方案吗?我的意思是...为什么不使用具有属性object Data 的非泛型接口IFoo
【解决方案2】:

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

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;

【讨论】:

  • 这可能是一个不错的选择,我会考虑。谢谢
【解决方案3】:

我做了一个实际适用于引用类型的示例,但是 - 正如@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");

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多