将嵌套集合转换为接口

本文关键字:接口 转换 集合 嵌套 | 更新日期: 2023-09-27 17:59:26

为什么?当然,我不需要用户定义的转换,因为List(T)IList(T)HashSet(T)IEnumerable(T)。谢谢

无法将类型"System.Collections.Generic.List<System.Collections.Generic.HashSet<string>>"隐式转换为"System.Collections.Generic.IList<System.Collections.Generic.IEnumerable<string>>"。存在显式转换(是否缺少强制转换?)

class Program {
    static IList<IEnumerable<string>> GetSet() {
        return new List<HashSet<string>>();
    }
}

将嵌套集合转换为接口

因为IList<T>是不变的。

为了说明为什么这是一个问题,请考虑以下示例:

例如,IList<T>Add(T object)IEnumerable<string>提供了一个方法,这将与构造函数表达式new List<HashSet<string>>()冲突。这意味着我可以调用您的program.GetSet()并添加一个new ArrayList<string>(),但您构建的实例不允许这样做,因为它有一个只包含HashSet<string>实例的合同(当有人询问包含ArrayList<T>GetSet()的内容时,您会返回什么?

typeparameter是双重嵌套的这一事实并不重要。例如,IList<Object>也不是IList<FooClass>的超类。


IEnumerable<T>本身并非如此(意味着IEnumerable<T>也是IEnumerable<SuperT>),因为IEnumerable<T>的唯一功能是输出值。这是允许的,因为利斯科夫替代原则。

该原则表明,当在类层次结构中向下走时返回类型只能变得更通用(超类/接口)并且参数类型只能变得更加具体(sub类/接口)。


C#通过在泛型类型声明中使用inout关键字,提供了处理这一原理(称为方差)的工具。

例如,如果您确定Foo<SubT>也是Foo<T>,则可以将其定义为:

public class Foo<out T> {
    T getResult () {
        //do something
    }
}

在这种情况下,CCD_ 27相对于CCD_ 28是协变的。如果类型参数仅用作输入,则可以进一步指定。例如,在以下定义中,Bar<T>显然是Bar<SubT>的特例:

public class Bar<in T> {
    void setParameter(T parameter) {
        //do something
    }
}

我想我真正需要的是在C#泛型的上下文中对方差-逆方差和协方差-的简单解释,我在这里和这里都找到了:

错误信息并没有真正让我想到这一点,但现在我要总结一下:

对比

泛型类当然是类的模板,而不是类定义,可以使用中的关键字使其成为反变量。反向类允许从基类实例到派生类实例的赋值,即BorderCorlie=Dog

public interface AllowAssignmentsFromBaseToDerived<in T>

协方差

泛型类可以使用out关键字使协变。协变类允许从派生类实例到基类实例的赋值,即Dog=BorderCorlie

public interface AllowAssignmentsFromDerivedToBase<out T>

自C#1.0以来,数组类型、自C#2.0以来的委托类型以及自C#4.0以来的泛型类型参数都支持方差。

如果能有更多的回复来涵盖我错过的更多点,我会很高兴,但仍然觉得有点信息不足。

更多信息从这里获得:

我如何自己创建变体通用接口和委派

out关键字将类型参数标记为协变,in关键字将其标记为反变体。最重要的两条规则记住:

  1. 如果仅使用泛型类型参数,则可以将其标记为协变作为方法返回类型,不用作形式方法的类型参数。

  2. 反之亦然,如果一个类型仅用作形式化方法参数的类型,而不用作方法返回类型。

    interface IVariant<out R, in A>
    {
        // These methods satisfy the rules.
        R GetR();
        void SetA(A sampleArg);
        R GetRSetA(A sampleArg);
        // And these don’t.
        // A GetA();
        // void SetR(R sampleArg);
        // A GetASetR(R sampleArg);
    }

此外,如果您扩展了一个变体通用接口,它在默认情况下是不变的。您需要根据需要指定In或Out。

最后,由于我的解释将严重不足,请尝试Eric Lippert的博客