【问题标题】:Is there a way to downcast a generic class to an instance with a derived T?有没有办法将泛型类向下转换为具有派生 T 的实例?
【发布时间】:2017-04-28 21:51:04
【问题描述】:

假设我有以下类用于包装给定类型的一些项目:

public class IndexedModelWithValue<T>
{
    public IndexedModelWithValue(T value, int index)
    {
        Index = index;
        InnerValue = value;
    } 

    public int index;

    public T InnerValue { get; }
}

我有这两个类:

class Animal { }
class Dog : Animal { }

在某些时候,我加载了不同的对象(这里我们将只使用 Dog 类),它们都继承自同一个基类型,所以我会有这样的东西:

Dog dog = LoadMyDerivedAnimalFromSomewhere();
Animal animal = dog;

// In my actual code I have something like
IEnumerable<Animal> animals = LoadMyDerivedAnimalsFromSomewhere();

然后继续包装所有这些项目,以便我最终得到如下内容:

IndexedModelWithValue<Animal> indexedAnimal = new IndexedModelWithValue(animal, 0);

现在,我知道索引模型中的动物实际上是一个 Dog 实例,那么有没有办法从那个实例中得到一个 IndexedModelWithValue&lt;Dog&gt; 对象?

我考虑过使用接口(我不介意以IIndexedModelWithValue&lt;Dog&gt; 之类的东西结束,只要我在那里得到Dog 类型),但据我了解,协变类只能强制转换在相反的方向(IEnumerable&lt;String&gt;IEnumerable&lt;object,反之亦然),我不能在这里使用逆变类,因为该类公开了一个 T 参数。

感谢您的帮助!

编辑:为澄清起见,这是我最终的清单:

IEnumerable<Animal> animals = LoadAllMyAnimalsFromSomewhere();
IEnumerable<IndexedModelWithValue<Animal>> = animals.Select((item, i) => new IndexedModelWithValue(item, i));

临时(坏)解决方法:现在我使用的解决方案是向索引类添加一个方法:

public IndexedModelWithValue<TValue> Downcast<TValue>() where TValue : T
{
    return new IndexedModelWithValue<TValue>((TValue)InnerValue, Index);
}

这样我就可以做到:

IndexedModelWithValue<Dog> indexedDog = indexedAnimal.Downcast<Dog>();

唯一的问题是这样我每次调用这个方法时都会创建IndexedModelWithValue类的一个新实例(所以我在浪费内存和CPU时间)而事实上我只想将该对象转换为我的派生类型。


解决方案:这是我现在用来解决问题的方法:

public static IEnumerable<ICovariantIndexedModelWithValue<T>> New([NotNull] IEnumerable<T> source)
{
    int index = 0;
    foreach (T item in source)
    {
        object instance = Activator.CreateInstance(typeof(IndexedModelWithValue<>).MakeGenericType(item.GetType()), item, index);
        yield return (ICovariantIndexedModelWithValue<T>)instance;
        index++;
    }
}

我可以调用它并传递原始列表以获得协变包装列表,其中每个单独的项目都具有“正确的类型”,具体取决于其派生类型。

界面如下:

public interface ICovariantIndexedModelWithValue<out T>
{
    int Index { get; }

    T InnerValue { get; }
}

【问题讨论】:

    标签: c# generics covariance


    【解决方案1】:

    您的indexedDogindexedAnimal 是两种不同的类型。不仅仅是您将它们存放在不同形状的盒子中,实际类型也不同。因此,如果不按原样创建新对象,就无法做到这一点。如果您想避免浪费内存、cpu 或其他任何东西,那么您首先需要将对象创建为IndexedModelWithValue&lt;Dog&gt;。如果你知道它绝对是一只狗,那么这就是你应该做的。如果你有一只确定的狗,那么首先不要创建IndexedModelWithValue&lt;Animal&gt;,你的问题就会消失。在没有看到创建对象的代码的情况下,很难确切说明如何执行此操作,但泛型应该允许您创建所需的类型。

    【讨论】:

    • 问题是我有不同的嵌套类型,它们由一个方法作为基类型的单个列表返回,我使用该列表来创建索引实例。我可以为索引类使用变体接口并为其分配派生类型(以便每个元素都具有“正确”类型),但我仍然不知道如何从单个 @987654326 生成派生 IndexedModelWithValue 实例@列表。
    • 你怎么会神奇地知道你肯定有一只狗呢?你真的需要IndexedModelWithValue&lt;Dog&gt;而不是IndexedModelWithValue&lt;Animal&gt;吗?如果您真的必须从一种类型转换为另一种类型,是否真的会导致性能问题,以至于您需要担心这一点?
    • 我知道这是因为我使用DataTemplateSelector 来检查模型内部值的实际类型并选择正确的DataTemplate。在每个不同的模板中都使用强制类型转换来强类型化 DataContext 属性,因为我需要它才能使用已编译的绑定(它是一个 UWP 应用程序)。
    【解决方案2】:

    如果对象本身是强类型创建的(例如,当为 Dog 创建 IndexedModelWithValue 时,它被创建为 IndexedModelWithValue&lt;Dog&gt; 而不是 IndexedModelWithValue&lt;Animal&gt; ),则可以使用接口来支持(协)方差和存储在接口类型列表中的所有不同值。

    edit 基于 Animal 类型的源:虽然就构建时间检查而言不是一个很好的解决方案,但可以使用 Activator.CreateInstancetypeof(IndexedModelWithValue&lt;&gt;).MakeGenericType(a.GetType())(其中 a 是 Animal 变量)。 (如果加载数据的方法可以以某种方式创建其他对象,则可以添加更好的设计时功能)

    根据您当前的示例,可以使用以下命令创建可枚举: LoadAllMyAnimalsFromSomewhere().Select((a,i)=&gt; (IIndexedModelWithValue&lt;Animal&gt;) Activator.CreateInstance(typeof(IndexedModelWithValue&lt;&gt;).MakeGenericType(a.GetType()), a,i));

    Linqpad 安全码:

    void Main()
    {
        var list = LoadData().Select((a,i)=> (IIndexedModelWithValue<Animal>) Activator.CreateInstance(typeof(IndexedModelWithValue<>).MakeGenericType(a.GetType()), a,i)).ToList();
    
        foreach(var t in list.OfType<IIndexedModelWithValue<Dog>>())
            Console.WriteLine(t);
    }
    
    static IEnumerable<Animal> LoadData()
    {
        yield return new Dog();
        yield return new Gecko();
    }
    
    public class IndexedModelWithValue<T>:IIndexedModelWithValue<T>
    {
        public IndexedModelWithValue(T value, int index)
        {
            Index = index;
            InnerValue = value;
        } 
    
        public int Index{get;set;}
    
        public T InnerValue { get; }
    }
    
    public interface IIndexedModelWithValue<out T>
    {
        int Index{get;}
        T InnerValue {get;}
    }
    
    class Animal { }
    class Dog : Animal { }
    class Gecko:Animal{}
    

    【讨论】:

    • 我已经考虑过了,问题是当我创建IndexedModelWithValue 对象列表时,我只有加载所有数据的方法返回的基本IEnumerable&lt;Animal&gt; 列表,所以我不知道如何从那里创建强类型 IndexedModelWithValue 对象:/
    • 为此添加了一个选项。尽管不是对设计时间最友好的解决方案,但只要您知道输入(和构造函数),使用 MakeGenericType 动态创建类型应该足够安全。小旁注,如果涉及大型列表,最好使用类型构造函数保留字典,而不是使用 MakeGenericType 每种类型。
    猜你喜欢
    • 2022-06-21
    • 2016-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-26
    • 2019-08-31
    • 2020-10-15
    相关资源
    最近更新 更多