简体   繁体   English

C#中的协方差和逆变

[英]Covariance and Contravariance in C#

I will start by saying that I am Java developer learning to program in C#. 我将首先说我是Java开发人员,学习用C#编程。 As such I do comparisons of what I know with what I am learning. 因此,我将我所知道的与我正在学习的东西进行比较。

I have been playing with C# generics for a few hours now, and I have been able to reproduce the same things I know in Java in C#, with the exception of a couple of examples using covariance and contravariance. 我已经玩C#generics几个小时了,我已经能够在C#中重现我在Java中所知道的相同的事情,除了使用协方差和逆变的几个例子。 The book I am reading is not very good in the subject. 我正在阅读的这本书在这个主题上并不是很好。 I will certainly seek more info on the web, but while I do that, perhaps you can help me find a C# implementation for the following Java code. 我肯定会在网上寻求更多信息,但是当我这样做时,也许你可以帮我找到以下Java代码的C#实现。

An example is worth a thousand words, and I was hoping that by looking a good code sample I will be able to assimilate this more rapidly. 一个例子胜过千言万语,我希望通过寻找一个好的代码示例,我将能够更快地吸收它。

Covariance 协方差

In Java I can do something like this: 在Java中,我可以这样做:

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

I can use this code as follows: 我可以使用以下代码:

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);

Now I did discover that C# supports covariance/contravariance only on interfaces and as long as they have been explicitly declared to do so (out/in). 现在我确实发现C#仅在接口上支持协方差/逆变,只要它们已被明确声明(out / in)。 I think I was not able to reproduce this case, because I could not find a common ancestor of all numbers, but I believe that I could have used IEnumerable to implement such thing if a common ancestor exists. 我想我无法重现这个案例,因为我找不到所有数字的共同祖先,但我相信如果存在共同的祖先,我可以使用IEnumerable来实现这样的事情。 Since IEnumerable is a covariant type. 因为IEnumerable是协变类型。 Right? 对?

Any thoughts on how to implement the list above? 有关如何实施上述列表的任何想法? Just point me into the right direction. 请指出我正确的方向。 Is there any common ancestor of all numeric types? 是否有所有数字类型的共同祖先?

Contravariance 逆变

The contravariance example I tried was the following. 我试过的逆变例如下。 In Java I can do this to copy one list into another. 在Java中,我可以将一个列表复制到另一个列表中。

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

Then I could use it with contravariant types as follows: 然后我可以使用逆变类型,如下所示:

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

My basic problem, trying to implement this in C# is that I could not find an interface that was both covariant and contravariant at the same time, as it is case of List in my example above. 我的基本问题是,尝试在C#中实现这个问题是我找不到同时具有协变性和逆变性的接口,就像我上面的例子中的List一样。 Maybe it can be done with two different interfaces in C#. 也许它可以用C#中的两个不同接口完成。

Any thoughts on how to implement this? 有关如何实现这一点的任何想法?

Thank you very much to everyone for any answers you can contribute. 非常感谢大家提供的任何答案。 I am pretty sure I will learn a lot from any example you can provide. 我很确定我会从你能提供的任何例子中学到很多东西。

Rather than answering your questions directly I'm going to answer some slightly different questions: 而不是直接回答你的问题,我将回答一些略有不同的问题:

Does C# have a way to genericize over types that support arithmetic operators? C#是否有办法对支持算术运算符的类型进行泛化?

Not easily, no. 不容易,没有。 It would be nice to have the capability of making a Sum<T> method that could add integers, doubles, matrices, complex numbers, quaternions... and so on. 能够生成Sum<T>方法可以添加整数,双精度,矩阵,复数,四元数等等,这将是很好的。 Though this is a fairly frequently requested feature, it is also a big feature and it has never been high enough on the priority list to justify its inclusion in the language. 虽然这是一个相当频繁的请求功能,但它也是一个很大的功能,它在优先级列表上从来没有足够高,以证明它包含在语言中。 I would personally like it, but you should not expect it in C# 5. Perhaps in a hypothetical future version of the language. 我本人也喜欢它,但是你不应该期待它在C#5中。也许在假设的未来版本的语言中。

What is the difference between Java's "call site" covariance/contravariance and C#'s "declaration site" covariance/contravariance? Java的“呼叫站点”协方差/逆变与C#的“声明站点”协方差/逆变之间有什么区别?

The fundamental difference at the implementation level is of course that as a practical matter, Java generics are implemented via erasure; 实现层面的根本区别当然是,作为一个实际问题,Java泛型是通过擦除实现的; though you get the benefits of a pleasant syntax for generic types, and compile-time type checking, you don't necessarily get the performance benefits or runtime type system integration benefits that you would in C#. 虽然您可以获得通用类型的令人愉快的语法和编译时类型检查的好处,但您不一定会获得C#中的性能优势或运行时类型系统集成优势。

But that's really more of an implementation detail. 但这更像是一个实现细节。 The more interesting difference from my perspective is that Java's variance rules are enforced locally and C#'s variance rules are enforced globally . 从我的观点来看,更有趣的区别是Java的方差规则在本地强制实施,C#的方差规则在全球范围内实施。

That is to say: certain variant conversions are dangerous because they imply that certain not-type-safe operations will not be caught by the compiler. 也就是说:某些变体转换是危险的,因为它们暗示编译器不会捕获某些非类型安全的操作。 The classic example is: 经典的例子是:

  • A tiger is a mammal. 老虎是一种哺乳动物。
  • A list of X is covariant in X. (Suppose.) X的列表在X中是协变的。(假设。)
  • A list of tigers is therefore a list of mammals. 因此,老虎名单是哺乳动物的清单。
  • A list of mammals can have a giraffe inserted. 哺乳动物名单可以插入长颈鹿。
  • Therefore you can insert a giraffe into a list of tigers. 因此,您可以将长颈鹿插入老虎列表中。

Which clearly violates type safety, as well as the safety of the giraffe. 这显然违反了类型安全性以及长颈鹿的安全性。

C# and Java use two different techniques to prevent this type safety violation. C#和Java使用两种不同的技术来防止这种类型的安全违规。 C# says that when the I<T> interface is declared, if it is declared as covariant then there must be no method of the interface which takes in a T . C#表示当声明I<T>接口时,如果它被声明为协变,则必须没有接收T的接口方法 If there is no method for inserting a T into a list, then you will never insert a giraffe into a list of tigers because there is no method for inserting anything . 如果没有方法将T插入列表,那么你永远不会将长颈鹿插入老虎列表,因为没有插入任何东西的方法。

Java by contrast says that at this local site, we get to treat the type covariantly and we promise not to call any methods right here that might violate type safety. 相比之下,Java说在这个本地站点,我们可以协同处理这种类型,我们保证不会在这里调用任何可能违反类型安全的方法。

I do not have enough experience with Java's feature to say which is "better" under what circumstances. 我没有足够的Java功能经验来说明在哪种情况下“更好”。 The Java technique is certainly interesting. Java技术当然很有趣。

For the 2nd part to your question, you don't need contravariance, all you need to do is state that the first type can be cast to the second. 对于你的问题的第二部分,你不需要逆变,你需要做的就是声明第一种类型可以转换为第二种类型。 Again, use the where TSource: TDest syntax to do this. 再次,使用where TSource: TDest语法来执行此操作。 Here is a full example (that shows how to do it with an extension method): 这是一个完整的示例(显示如何使用扩展方法执行此操作):

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();
    }

You can use the IConvertible interface: 您可以使用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;
}

Note the generic constraint ( where T : IConvertible ), which is similar to the extends in Java. 请注意泛型约束( where T : IConvertible ),它类似于Java中的extends

There is no base Number class in .NET. .NET中没有基数Number类。 The closest you can get might look something like this: 你可以得到的最接近的可能是这样的:

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;
}

You would have to catch any errors that occur during the Convert.ToDouble in case any object in the list is not numeric and does not implement IConvertible . 如果列表中的任何对象不是数字且未实现IConvertible ,则必须捕获Convert.ToDouble期间发生的任何错误。

Update 更新

In this situation, though, I'd personally use an IEnumerable and a generic type ( and, thanks to Paul Tyng , you can force T to implement IConvertible ): 但是,在这种情况下,我个人使用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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM