C#中的协方差和反方差

本文关键字:方差 | 更新日期: 2023-09-27 18:28:36

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

我已经用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#中的两个不同接口来完成。

对如何实现这一点有什么想法吗?

非常感谢大家提供的答案。我相信我会从你提供的任何例子中学到很多东西。

C#中的协方差和反方差

与其直接回答您的问题,不如回答一些略有不同的问题:

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

不容易。如果有能力制作一个Sum<T>方法,可以添加整数、二重、矩阵、复数、四元数。。。尽管这是一个经常被要求的功能,但它也是一个很大的功能,而且它在优先级列表中的位置从来都不足以证明它被包含在语言中。我个人很喜欢它,但你不应该期望它出现在C#5中。也许是在一个假设的未来版本的语言。

Java的"调用站点"协方差/反方差和C#的"声明站点"方差/反方差之间有什么区别?

实现级别的根本区别当然是,实际上,Java泛型是通过擦除实现的;尽管您可以获得泛型类型的愉快语法和编译时类型检查的好处,但您不一定能获得C#中的性能优势或运行时类型系统集成优势。

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

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

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

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

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

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

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

对于问题的第二部分,您不需要逆变,您只需要声明第一种类型可以转换为第二种类型。再次使用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();
    }

您可以使用IConvertible接口:

public static decimal sum<T>(IEnumerable<T> numbers) where T : IConvertible
{
    decimal summation = 0.0m;
    foreach(var number in numbers){
        summation += number.ToDecimal(System.Globalization.CultureInfo.InvariantCulture);
    }
    return summation;
}

注意通用约束(where T : IConvertible),它类似于Java中的extends

.NET中没有基本的Number

public static double sum(List<object> numbers) {
    double summation = 0.0;
    var parsedNumbers = numbers.Select(n => Convert.ToDouble(n));
    foreach (var parsedNumber in parsedNumbers) {
        summation += parsedNumber;
    }
    return summation;
}

如果列表中的任何对象不是数字并且没有实现IConvertible,则必须捕获在Convert.ToDouble期间发生的任何错误。

更新

不过,在这种情况下,我个人会使用IEnumerable和泛型类型(而且,由于Paul Tyng,您可以强制T实现IConvertible):

public static double sum<T>(IEnumerable<T> numbers) where T : IConvertible {
    double summation = 0.0;
    var parsedNumbers = numbers.Select(n => Convert.ToDouble(n));
    foreach (var parsedNumber in parsedNumbers) {
        summation += parsedNumber;
    }
    return summation;
}