有没有办法将泛型类向下转换为带有派生T的实例?

时间:2017-04-28 12:04:57

标签: c# generics covariance

我们说我有以下用于包装给定类型的项目的类:

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<Dog>个对象?

我考虑过使用界面(只要我在那里获得IIndexedModelWithValue<Dog>类型,我就不会想到Dog之类的东西),但据我所知,协变类只能在相反的方向上投射(IEnumerable<String>IEnumerable<object而反之亦然),而且我不能在这里使用逆变类,因为类暴露了T参数。 / p>

感谢您的帮助!

修改:有关说明,请参阅我的列表:

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++;
    }
}

我可以调用它并传递原始列表以获得协变包装列表,其中每个单独的项目具有&#34;右类型&#34;取决于其派生类型。

界面如下所示:

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

    T InnerValue { get; }
}

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

如果对象本身是强类型创建的(例如,当为IndexedModelWithValue创建Dog时,它将被设为IndexedModelWithValue<Dog>而不是IndexedModelWithValue<Animal>) ,接口可用于支持(co)方差和存储在接口类型列表中的所有不同值。

编辑基于类型Animal的源:尽管构建时间检查不是一个非常好的解决方案,但可以创建强类型IndexedModelWithValue Activator.CreateInstancetypeof(IndexedModelWithValue<>).MakeGenericType(a.GetType())(其中a是Animal变量)。 (如果加载数据的方法可能以某种方式创建其他对象,则可以添加更好的设计时功能)

根据您当前的示例,可以使用以下命令创建枚举: LoadAllMyAnimalsFromSomewhere().Select((a,i)=> (IIndexedModelWithValue<Animal>) Activator.CreateInstance(typeof(IndexedModelWithValue<>).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{}