Covariance and Contravariance in C#

青春壹個敷衍的年華 提交于 2019-12-05 01:14:49
markmuetz

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

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?

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. 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.

What is the difference between Java's "call site" covariance/contravariance and C#'s "declaration site" covariance/contravariance?

The fundamental difference at the implementation level is of course that as a practical matter, Java generics are implemented via erasure; 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#.

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.

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.)
  • 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# 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. 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.

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.

I do not have enough experience with Java's feature to say which is "better" under what circumstances. The Java technique is certainly interesting.

You can use the IConvertible interface:

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.

There is no base Number class in .NET. 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.

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

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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!