如何创建一个 List(of T),其中 T 是 (T) 的接口,并且 List 的内容继承了实现 T 接口的 Abstract 类

Posted

技术标签:

【中文标题】如何创建一个 List(of T),其中 T 是 (T) 的接口,并且 List 的内容继承了实现 T 接口的 Abstract 类【英文标题】:How can I create a List(of T) where T is an interface of (T) and the contents of the List inherit an Abstract class that implements the interface of T 【发布时间】:2021-11-09 21:42:37 【问题描述】:

我正在尝试实现一些通用配置类型,但遇到了转换问题。理想情况下,我想要一个强类型的配置值实例,可以分配给我们不同的导出器。

我拥有的是:

IConfigurableExport:将导出器标记为可配置的接口,并包含 ICollection<IExportConfiguration<Object>> Configurations 属性 IExportConfiguration<T>:定义通用属性(名称、描述等)的接口 ExportConfiguration<T>:实现 IExportConfiguration<T> 的抽象类,将接口属性设置为 Abstract,添加一些常用功能,例如从配置值列表中设置自身的值等等 ConfigurationInstance<bool>: 继承 ExporterConfiguration<Boolean>

配置导出器的 UI 是根据 IConfigurableExport.Configurations 中的值动态构建的。当我尝试调用 IConfigurableExport.Configurations 时出现转换错误。下面是一些示例代码来说明问题:

using System;
using System.Linq;
using System.Collections.Generic;
                    
public class Program

    public static void Main()
    
        var t = new MyExporter();
        var configs = t.Configurations;
        foreach(var conf in configs)
            Console.WriteLine(conf.Name);
        
    


public interface IExportConfiguration<T> 
    string Description get;
    Guid Id get;
    string Name get;
    T Value get; set;



public abstract class ExportConfiguration<T>
    : IExportConfiguration<T>

    public abstract string Description  get; 
    public abstract string Name  get; 
    public abstract Guid Id  get; 
    public abstract T Value  get; set; 
    public abstract T DefaultValue  get; set; 
    
    public virtual void SetValue(ICollection<KeyValuePair<Guid, object>> values)
    
        if (values.Any(kvp => kvp.Key == this.Id))
            Value = (T)values.First(kvp => kvp.Key == this.Id).Value;
        else
            Value = this.DefaultValue == null ? this.DefaultValue : default(T);
    


public interface IConfigurableExport 
    ICollection<IExportConfiguration<object>> Configurations get;


public class MyConfig : ExportConfiguration<bool> 
    public override string Description  get  return "This is my description"; 
    public override string Name  get return "My Config Name"; 
    public override Guid Id  get  return Guid.Parse("b6a9b81d-412d-4aa8-9090-37c9deb1a9f4"); 
    public override bool Value  get; set;
    public override bool DefaultValue  get; set;


public class MyExporter : IConfigurableExport 
    MyConfig conf = new MyConfig();
    public ICollection<IExportConfiguration<object>> Configurations 
        get 
            return new List<IExportConfiguration<object>>  (IExportConfiguration<object>)conf ;
        
    

Here is a dontnetfiddle说明问题

【问题讨论】:

也许你可以有另一个抽象类class ExportConfigurationBase,它声明了所有的非泛型属性,然后声明列表IReadOnlyCollection&lt;IExportConfigurationBase&gt; Configurations get;参见小提琴例如dotnetfiddle.net/lRaL2S。最终,IConfigurableExport 也需要是通用的,才能拥有对 IExportConfiguration&lt;T&gt; 的强类型引用 在我的理解中我可能有点离题,但是当MyConfig 声明中使用的具体类型是bool 时,您有四个对object 类型的引用。如果您将abstract class ExportConfiguration&lt;T&gt; 中函数SetValue 的声明更改为T 类型,而将其他引用更改为bool,它应该可以工作。但我不能确定它在实践中对你有用。 This dotnetfiddle 展示了我做了什么以及它是如何工作的 @JayV,感谢您的建议,但我们的想法是在以后支持整数和字符串。作为权宜之计,我可以通过这种方式实现它,但是稍后当我们想要支持整数时,我们将面临同样的问题。 @Charlieface,感谢您的想法,但我看到的这个实现的问题是在运行时从配置项中取回值,这就是当前在接口本身上定义值的原因。 我假设您希望能够获取和设置,此时协方差无论如何都不是一个选项 【参考方案1】:

主要问题是IExportConfiguration&lt;T&gt;定义了一个T Value get; set;

如果您有IExportConfiguration&lt;bool&gt;,则不能将IExportConfiguration&lt;bool&gt; 转换为IExportConfiguration&lt;object&gt;,因为您不能将Value 设置为object,它必须设置为bool 以确保其类型安全。

要制作这种类型的演员表,您需要使用covariant interface,如下所示

public interface IExportConfiguration<T> : IReadOnlyExportConfiguration<T>
    new T Value get; set;


//this interface is covariant, and if you have a IReadOnlyExportConfiguration<string>
//it can be cast to an IReadOnlyExportConfiguration<object>
public interface IReadOnlyExportConfiguration<out T> 
    string Description get;
    Guid Id get;
    string Name get;
    T Value get;

然后您可以将您的可配置导出重新定义为

public interface IConfigurableExport 
    ICollection<IReadOnlyExportConfiguration<object>> Configurations get;

但是您不能将MyConfig 用作布尔配置,因为IReadOnlyExportConfiguration&lt;bool&gt; 不会转换为IReadOnlyExportConfiguration&lt;object&gt;,因为布尔是一种简单的数据类型。 T 的类型必须是类类型才能与object 协变。

public class MyConfig : ExportConfiguration<string> 
    public override string Description  get  return "This is my description"; 
    public override string Name  get return "My Config Name"; 
    public override Guid Id  get  return Guid.Parse("b6a9b81d-412d-4aa8-9090-37c9deb1a9f4"); 
    public override string Value  get; set;
    public override string DefaultValue  get; set;

最后你的出口商变成了

public class MyExporter : IConfigurableExport 
    MyConfig conf = new MyConfig();
    public ICollection<IReadOnlyExportConfiguration<object>> Configurations 
        get 
            return new List<IReadOnlyExportConfiguration<object>>  (IReadOnlyExportConfiguration<object>)conf ;
        
    

但此时您将无法为您的配置设置值,如果需要,您将需要重新构建您的解决方案。

【讨论】:

这太棒了!我在 dotnetfiddle 中对其进行了一些调整,以使其完全按照我的意愿工作! Here is a fiddle 与您建议实施的更改。我会将其标记为已接受的答案,并使用新的解决方案更新我的问题。非常感谢您的帮助。 所以我对此做了一些额外的测试,这似乎只适用于字符串,你知道为什么我不能将它与 bool 或 ints 一起使用吗? @AdamH 我在回答中特别说你不能使用bool 或其他简单的数据类型,因为它们与对象不协变(基本上你不能从简单的值类型隐式转换到object的类型)更彻底的答案在这里***.com/questions/12454794/… 感谢您的信息,感谢您的宝贵时间。

以上是关于如何创建一个 List(of T),其中 T 是 (T) 的接口,并且 List 的内容继承了实现 T 接口的 Abstract 类的主要内容,如果未能解决你的问题,请参考以下文章

C#如何把List of Object转换成List of T具体类型

如何销毁 List Of(T),重新分配列表使用的内存

如何将 List<T> 转换为 DataSet?

如何将 List<T> 转换为 DataSet?

C#中List(Of T)类

VB.NET ArrayList 到 List(Of T) 类型的复制/转换