通用接口的通用列表不允许,有任何替代方法吗?

31

我正试图找到使用通用接口的通用列表作为变量的正确方式。

这里有一个示例。它可能不是最好的,但希望你能理解:

public interface IPrimitive<T>
{
     T Value { get; }
}

然后在另一个类中,我希望能够声明一个变量来保存实现了任意TIPrimitive<T>接口的对象列表。

// I know this line will not compile because I do not define T   
List<IPrimitive<T>> primitives = new List<IPrimitives<T>>;

primitives.Add(new Star());   // Assuming Star implements IPrimitive<X>
primitives.Add(new Sun());    // Assuming Sun implements IPrimitive<Y>

请注意,在“IPrimitive”中的“T”可能对于列表中的每个条目都不同。
如何设置这样的关系?还有其他方法吗?

1
很好的问题,挖掘这个问题花了一些时间。针对那些从Java过来的跨语言链接:Java使用泛型与列表和接口 - childno͡.de
4个回答

31
public interface IPrimitive
{

}

public interface IPrimitive<T> : IPrimitive
{
     T Value { get; }
}

public class Star : IPrimitive<T> //must declare T here
{

}

那么您应该能够拥有
List<IPrimitive> primitives = new List<IPrimitive>;

primitives.Add(new Star());   // Assuming Star implements IPrimitive
primitives.Add(new Sun());    // Assuming Sun implements IPrimitive

6
如果使用IPrimitive而不是IPrimitive<T>列表,你将无法获取列表中项的Value值,因为Value在IPrimitive<T>中定义,而不是IPrimitive中定义。 - P B
4
使用这种方法,IPrimitive 将会被定义为具有属性 object Value { get; },然后 Star 需要为 IPrimitive<T>IPrimitive 提供实现。例如:public int Value { get { return _value; } } 来实现 IPrimitive<int>,以及 object IPrimitive.Value { get { return this.Value; } } 显式地处理非泛型接口。 - Anthony Pegram
@user414076 但这将让您引入您最初想要避免使用的接口/签名!VisualStudio会告诉我,IPrimitive.Value (object Value { get; set; })被IPrimitive<T>.Value (T Value { get; set; })隐藏,如果有意隐藏,则应使用new关键字。这样做(new T Value { get; set; }),仍然需要强制Star实现两个接口 ;/ - childno͡.de
13
但是...但是...太阳是一颗星星! - user3791372
我无法调用方法 T Value { get; } - CESAR NICOLINI RIVERO

11

John是正确的。

如果你正在使用C#4,我还可以建议您使接口协变。

public interface IPrimitive<out T>
{
     T Value { get; }
}

这可以在您需要从列表中取出物品时帮您省去一些麻烦。

1
T将被定义,但它是在实现T的每个对象中定义的。因此,在上面的示例中,它将在Star和Sun中定义。因此,当我去在另一个类中创建通用列表时,它不知道T是什么。在Star和Sun中,T可能是不同的类型。 - P B
@Andrew:我看到他在评论中澄清,他需要每个列表项可能有不同的T。在这种情况下,Josh答案是正确的。无论如何,我认为我应该删除我的答案,但你的答案应该被编辑以指向剩余的答案。 - John Saunders
1
微软在这个页面中解释了泛型接口的差异性:请看一下,它可能会有很大帮助:泛型接口集合中的差异性 - user12805184

3

您说它无法工作,因为您没有定义T。那么请定义它:

public class Holder<T>
{
    public List<IPrimitive<T>> Primitives {get;set;}
}

T将被定义,但它在实现T的每个对象中都有不同的定义。因此,在上述示例中,它将在Star和Sun中分别定义。因此,当我在另一个类中尝试创建泛型列表时,它并不知道T是什么。在Star和Sun中,T可能是不同的类型。这仍然可以工作吗? - P B
@PB:不行。List<T> 中只能有一个 T - John Saunders
1
T在接口中定义,而不是持有它的类。这是不同的。 - iroel
@iroel 接口无法定义类型。它们只能定义方法、属性、索引器和事件。请参阅 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface - John Saunders
你没有理解问题。他不想定义仅用于保存T集合的类。他只想要一个被定义为IPrimitive<T>集合的变量/字段。这是不同的。 - iroel
@iroel 是的,它不同:这是不可能的! - John Saunders

2

这是C#语言中最复杂的元素之一,尽管它对于构建定义良好的组件非常重要。因此,C#有所不足。但是,肯定可以让它工作。

诀窍在于有3个部分:

  1. 一个非泛型接口,其中包含接口的所有要求。
  2. 一个泛型抽象类,实现非泛型接口并根据需要执行类型转换。
  3. 一个实现了适当类型结果的泛型抽象类的类

例如:

public interface INonGenericInterface{
    void Execute(object input);
    object GetModel();
}

public abstract class IGenericInterfaceBase<T> : INonGenericInterface{
    void INonGenericInterface.Execute(object input){
        Execute((T) input);
    }

    object INonGenericInterface.GetModel(){
        return GetModel();
    }

    protected abstract void Execute(T input);
    protected abstract T GetModel();
}

public class ImplementingClass : IGenericInterfaceBase<ModelClass>{
    protected override void Execute(ModelClass input){ /*Do something with the input */ }  
    protected override ModelClass GetModel(){ return new ModelClass();}
}

//Extras for demo
public class ModelClass { }
public class ModelClass2 { }

public class ImplementingClass2 : IGenericInterfaceBase<ModelClass2>
{
    protected override void Execute(ModelClass2 input) { /*Do something with the input */ }
    protected override ModelClass2 GetModel() { return new ModelClass2(); }
}

var agi = new INonGenericInterface[] { new ImplementingClass(), new ImplementingClass2() };
agi[0].Execute(); var model = agi[0].GetModel();
agi[1].Execute(); var model2 = agi[1].GetModel();
//Check the types of the model and model2 objects to see that they are appropriately typed.

这种结构在协调类之间非常有用,因为您可以指示实现类将使用多个类,并且类型检查将验证每个类是否符合已建立的类型期望。此外,您可能会考虑使用实际类而不是对象来表示非泛型类,以便可以对各种非泛型调用的结果执行函数。使用相同的设计,您可以使这些类成为具有自己实现的泛型类,从而创建非常复杂的应用程序。
给OP:请考虑更改接受的答案,以提高正确方法的认识,因为以前提出的所有答案都存在各种原因的缺陷,并且可能会让读者产生更多问题。这应该处理与集合中的泛型类相关的所有未来问题。

这个解决方案对我来说解决了问题。 - Emmanuel
这是一个非常好的答案! - georanto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接