C 中的协方差和逆变#

2022-09-04 05:54:07

我首先要说的是,我是Java开发人员,正在学习用C#编程。因此,我将我所知道的与我正在学习的东西进行比较。

我已经使用C#泛型几个小时了,我已经能够在C#中重现我在Java中所知道的相同的东西,除了几个使用协方差和逆变的例子。我正在读的书在这个主题上不是很好。我肯定会在网络上寻找更多信息,但是当我这样做时,也许你可以帮我找到以下Java代码的C#实现。

一个例子胜过千言万语,我希望通过寻找一个好的代码样本,我能够更快地吸收它。

协方差

在Java中,我可以做这样的事情:

public static double sum(List<? extends Number> numbers) {
    double summation = 0.0;
    for(Number number : numbers){
        summation += number.doubleValue();
    }
    return summation;
}

我可以按如下方式使用此代码:

List<Integer> myInts = asList(1,2,3,4,5);
List<Double> myDoubles = asList(3.14, 5.5, 78.9);
List<Long> myLongs = asList(1L, 2L, 3L);

double result = 0.0;
result = sum(myInts);
result = sum(myDoubles)
result = sum(myLongs);

现在我确实发现C#仅在接口上支持协方差/逆变,并且只要它们已被显式声明(out/in)。我想我无法重现这种情况,因为我找不到所有数字的共同祖先,但我相信如果存在一个共同的祖先,我可以使用IEnumerable来实现这样的事情。由于 IEnumerable 是一种协变类型。右?

关于如何实现上面的列表的任何想法?只要给我指出正确的方向。所有数值类型都有共同的祖先吗?

逆方差

我尝试的逆变示例如下。在Java中,我可以这样做以将一个列表复制到另一个列表中。

public static void copy(List<? extends Number> source, List<? super Number> destiny){
    for(Number number : source) {
       destiny.add(number);
    }
}

然后,我可以将其与逆变类型一起使用,如下所示:

List<Object> anything = new ArrayList<Object>();
List<Integer> myInts = asList(1,2,3,4,5);
copy(myInts, anything);

我试图在C#中实现这一点的基本问题是,我找不到同时具有协变和逆变的接口,就像我上面示例中的List一样。也许可以在C#中使用两个不同的接口来完成。

关于如何实现这一点的任何想法?

非常感谢大家提供任何可以贡献的答案。我很确定我会从你能提供的任何例子中学到很多东西。


答案 1

我不会直接回答你的问题,而是回答一些略有不同的问题:

C# 是否有办法对支持算术运算符的类型进行泛化?

不容易,不。如果能够制作一个可以添加整数,双精度,矩阵,复数,四元数的方法,那就太好了...等等。虽然这是一个相当频繁的请求功能,但它也是一个很大的功能,它在优先级列表中从未高到足以证明将其包含在语言中是合理的。我个人喜欢它,但你不应该在C#5中期待它。也许在该语言的假设未来版本中。Sum<T>

Java的“调用站点”协方差/逆变和C#的“声明站点”协方差/逆变有什么区别?

当然,实现级别的根本区别在于,作为一个实际问题,Java泛型是通过擦除实现的。虽然您可以获得泛型类型令人愉悦的语法和编译时类型检查的好处,但不一定能获得在C#中具有的性能优势或运行时类型系统集成优势。

但这实际上更像是一个实现细节。从我的角度来看,更有趣的区别是Java的方差规则在本地强制执行,而C#的方差规则是全局强制执行的。

也就是说:某些变体转换是危险的,因为它们意味着编译器不会捕获某些非类型安全的操作。典型的例子是:

  • 老虎是一种哺乳动物。
  • X 的列表在 X 中是协变的(假设)。
  • 因此,老虎的名单就是哺乳动物的名单。
  • 哺乳动物列表可以插入长颈鹿。
  • 因此,您可以将长颈鹿插入老虎列表中。

这显然违反了类型安全,以及长颈鹿的安全。

C# 和 Java 使用两种不同的技术来防止此类型的安全冲突。C#说,当接口被声明时,如果它被声明为协变,那么一定没有接口的方法接受T。如果没有将T插入列表的方法,那么您永远不会将长颈鹿插入老虎列表中,因为没有插入任何内容的方法I<T>

相比之下,Java说,在这个本地站点,我们可以协变地处理类型,我们承诺不会在这里调用任何可能违反类型安全的方法。

我对Java的功能没有足够的经验来说明在什么情况下哪个“更好”。Java技术当然很有趣。


答案 2

对于问题的第二部分,您不需要逆变,您需要做的就是声明第一种类型可以转换为第二种类型。同样,请使用语法来执行此操作。下面是一个完整的示例(演示如何使用扩展方法执行此操作):where TSource: TDest

static class ListCopy
{
    public static void ListCopyToEnd<TSource, TDest>(this IList<TSource> sourceList, IList<TDest> destList)
        where TSource : TDest // This lets us cast from TSource to TDest in the method.
    {
        foreach (TSource item in sourceList)
        {
            destList.Add(item);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<int> intList = new List<int> { 1, 2, 3 };
        List<object> objList = new List<object>(); ;

        ListCopy.ListCopyToEnd(intList, objList);
        // ListCopyToEnd is an extension method
        // This calls it for a second time on the same objList (copying values again).
        intList.ListCopyToEnd(objList);

        foreach (object obj in objList)
        {
            Console.WriteLine(obj);
        }
        Console.ReadLine();
    }

推荐