无法将通用具体类型添加到通用接口的列表中。

huangapple go评论82阅读模式
英文:

Unable to add a generic concrete type to a list of generic interface

问题

以下是要翻译的内容:

背景:

我正在尝试创建一个可以存储不同类型/实现的人员的列表。然后,根据他们是什么类型的人以及他们的信息来自哪个数据源,将对此列表进行过滤和操作。

问题出在这里:

当试图将我的具体类型添加到针对我的接口进行类型化的列表时,无论泛型上的类型约束如何,都无法进行转换和执行强制转换(请参见下文)。

var people = new List<Person<IPerson>>();

people.Add(new Person<Student>()); // 编译错误:无法从Person<Student>转换为Person<IPerson>
people.Add(new Person<StaffMember>() as Person<IPerson>); // 从Person<StaffMember>到Person<IPerson>通过引用转换、装箱、拆箱等方式。

如果我更新我的列表以忽略“包装器” Person<> 的泛型类型,只存储人员类型,看起来这是有效的。因此,我的泛型方面的某些假设明显是错误的!

var people = new List<IPerson>();

people.Add(new Student()); // 这是有效的(至少在编译时是有效的)
people.Add(new StaffMember()); // 这是有效的(至少在编译时是有效的)

当前的实现:

public interface IPerson
{ }

public class Person<TPerson> where TPerson : IPerson
{
    // 各种通用属性在这里...
    public DataSource Source { get; set; }

    // 我的泛型属性
    public TPerson Record { get; set; } 
}

public class Student : IPerson
{
    // 学生特定属性
}

public class StaffMember : IPerson
{
    // 员工成员特定属性
}

有什么想法吗?

英文:

Background:

I am trying to create a list that can store different types/implementations of people. This list will then be filtered and operated on based on what type of person they are, and what data source their information has come from.

This is what is failing:

When attempting to add my concrete types to my list that is typed against my interface, it is failing convertion and unable to perform casts to (see below), regardless of the type constraint on my generic.

var people = new List&lt;Person&lt;IPerson&gt;&gt;();

people.Add(new Person&lt;Student&gt;()); // Compile error: Cannot convert from Person&lt;Student&gt; to Person&lt;IPerson&gt; 
people.Add(new Person&lt;StaffMember&gt;() as Person&lt;IPerson&gt;); from Person&lt;StaffMember&gt; to Person&lt;IPerson&gt; via reference conversion, boxing, unboxing etc.

If I update my list to ignore the "wrapper" Person&lt;&gt; generic type and just store the list of people types, it looks like this is valid. So something is clearly wrong with my assumptions around the generic side of things!

var people = new List&lt;IPerson&gt;();

people.Add(new Student()); // This is valid (at least at compile time)
people.Add(new StaffMember()); // This is valid (at least at compile time)

Current implementation:

public interface IPerson
{ }

public class Person&lt;TPerson&gt; where TPerson : IPerson
{
    // Various common props here...
    public DataSource Source { get; set; }

    // My generic prop
    public TPerson Record { get; set; } 
}

public class Student : IPerson
{
    // Student specific props
}

public class StaffMember : IPerson
{
    // Staff member specific props
}

Any ideas?

答案1

得分: 1

如果您希望Person&lt;T&gt;可以分配给Person&lt;IPerson&gt;,则需要将类型声明为协变(Covariant),并且需要使用接口来实现协变:

public interface IPerson { }
public interface IPerson&lt;out T&gt; {
    T Record { get; } // 不允许使用setter
}

public class Person&lt;TPerson&gt; : IPerson&lt;TPerson&gt; where TPerson : IPerson
{
    // 我的泛型属性
    public TPerson Record { get; set; }
}
public class Student : IPerson { }
...
var list = new List&lt;IPerson&lt;IPerson&gt;&gt;();
list.Add(new Person&lt;Student&gt;()); // 正常工作

这种协变会对IPerson&lt;T&gt;接口中的TPerson的使用方式产生一些限制。像T Record { get; }这样的属性是可以的,因为您返回的是类型T,而所有这些类型都是IPerson的一部分。但是像void MyMethod(T value)这样的方法不允许,因为它会允许在Person&lt;Student&gt;对象上调用一个接受StaffMember对象的方法,这是不允许的。

但是请考虑是否需要首先使用泛型,如果需要,请考虑命名。Person&lt;IPerson&gt;IPerson&lt;IPerson&gt;可能会容易引起混淆。

英文:

If you want Person&lt;T&gt; to be assignable to Person&lt;IPerson&gt; the type needs to be Covariant, and you need an interface to make covariant:

public interface IPerson { }
public interface IPerson&lt;out T&gt; {
    T Record { get; } // setter not allowed
 }

public class Person&lt;TPerson&gt; : IPerson&lt;TPerson&gt; where TPerson : IPerson
{
    // My generic prop
    public TPerson Record { get; set; }
}
public class Student : IPerson { }
...
var list = new List&lt;IPerson&lt;IPerson&gt;&gt;();
list.Add(new Person&lt;Student&gt;()); // works

This covariance will place some limits on how the TPerson can be used in the IPerson&lt;T&gt; interface. A property like T Record { get; } is fine, since you are returning a type T, and all such types are an IPerson. A method like void MyMethod(T value) is not fine, since it would allow calling a method on Person&lt;Student&gt; object with a StaffMember object, and that is not allowed.

But do consider if you need to use generics in the first place, and if you do, consider naming. Person&lt;IPerson&gt; or IPerson&lt;IPerson&gt; can easily be confusing.

答案2

得分: 0

所以,在泛型方面,我的假设显然有问题!
这似乎是情况,尽管我不清楚你的假设是什么。
泛型不支持组合类型。如果你的假设是 Person<Student> 的实例会有 StudentPerson<T> 的成员,那是不正确的。
泛型有时被称为 '模板',我认为这个名字有助于理解泛型。
抽象意义上,列表提供了什么?添加?移除?索引?如果你必须为每种类型编写一个列表,代码不会有太大变化。Student 的列表和 StaffMember 的列表的实现除了使用的类型不同之外是相同的。List<T> 是一种泛型类型。该实现是根据 T 编写的,而不是特定类型。它可以被视为创建任何类型列表的模板。
你不能有异构集合。如果你使用一个 ArrayList,从集合的角度来看,一切都是 object。如果你使用一个 List<T>,一切都是你为 T 指定的类型。
如果你定义了一个 List<IPerson>,你可以添加任何实现(或 '是')IPerson 接口的对象。如果 IPerson 接口有一个 Name 属性,你可以从任何 IPerson 获取 Name,无论它是 Student 还是 StaffMember
List<IPerson>List<Student>List<StaffMember> 是不同的独特类型。你不能将 StaffMember 添加到 List<Student>
你的包装类可以写成以下形式,并与 List<Person> 一起使用。

public class Person
{
    public Person(IPerson person)
    {
        Record = person;
        ...
    }

    public DataSource Source { get; set; }

    public IPerson Record { get; set; }
}
英文:

> So something is clearly wrong with my assumptions around the generic side of things!

That would seem to be the case although I'm not clear on what your assumptions are.

Generics don't support composing types. If your assumption is that an instance of a Person&lt;Student&gt; would have the members of Student and the members of Person&lt;T&gt;, that is not correct.

Generics are sometimes called 'templates' and I think that name is useful in understanding generics.

In an abstract sense, what does a list provide? Add? Remove? Index? If you had to write a list for every type, the code would not vary much. The implementation of a List of Student and a list of StaffMember would be the same except for the type used. List&lt;T&gt; is a generic type. The implementation is written in terms of T and not a specific type. It can be thought of as a template for creating a list of any type.

You can't have a heterogeneous collection. If you use an ArrayList, everything from the perspective of the collection is an object. If you use a List&lt;T&gt; everything is whatever you specified for T.

If you define a List&lt;IPerson&gt; you can add any object that implements (or 'is a') IPerson. If the IPerson interface has a Name property, you can get the Name from any IPerson regardless of whether it is a Student or StaffMember.

List&lt;IPerson&gt;, List&lt;Student&gt;, and List&lt;StaffMember&gt; are different unique types. You cannot add a StaffMember to a List&lt;Student&gt;.

Your wrapper class could be written as follows and used with a List&lt;Person&gt;.

public class Person
{
    public Person(IPerson person)
    {
        Record = person;
        ...
    }

    public DataSource Source { get; set; }

    public IPerson Record { get; set; }
}

huangapple
  • 本文由 发表于 2023年6月19日 20:08:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76506478.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定