13

I will start by saying that I am Java developer learning to program in 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. 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.

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:

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). 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. Since IEnumerable is a covariant type. 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.

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. Maybe it can be done with two different interfaces in 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.

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
  • 1
    I'm pretty sure you can't because the .NET team in their infinite wisdom did not provide a way to talk about classes of primitive types (e.g., number, floating point). So you cannot use operator + in generic methods on arguments of a generic type, and there's no way to specify that you should be able to. It's a glaring hole, but there it is. – siride Mar 21 '12 at 14:06
  • 1
    I asked a question a while ago with some similar concepts: http://stackoverflow.com/questions/5516459/constrain-type-to-allow-addition-subtraction-operations-in-c-sharp – qxn Mar 21 '12 at 14:08

4 Answers4

24

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I do agree in that both languages handle it differently. The fact that C# requires that covariance/contravariance features be explicitly declared on interfaces, and the restriction on using this feature only through this interfaces clearly highlights a significant difference with Java. But both languages ensure heap pollution cannot be commited at compile-time. Honestly, I like the control and simplicity of things in Java when it comes to using covariance and contravariance, because you do not need to explicitly predefine it. – Edwin Dalorzo Mar 21 '12 at 16:27
  • @edalorzo, what about runtime control? – Roman Royter Mar 21 '12 at 17:13
  • @Roman If you are not allowed to commit heap pollution at compile time, then your bytecodes or IL cannot be wrong at runtime. That's the whole point of how generics are implemented in both languages, as far as I can see. The only difference is that in Java generic types are implemented with type erasure, which means that some type information is discarded at compile-time. Therefore, it is possible, to commit heap pollution by using reflection or through the use of legacy APIs. However, IMHO this is something you must really intend to do, not something that you would unintentionally do in Java. – Edwin Dalorzo Mar 21 '12 at 17:26
  • @edalorzo, above Eric gives his view: we promise we won't do bad things, as opposed to you can't do it period (because it won't compile). Seems like the latter is safer because it makes it impossible to do intentionally or unintentionally. – Roman Royter Mar 21 '12 at 17:36
  • @Roman I totally agree. The .Net platform implementation of generics with reifiable types is better than the non-reifiable Java platform implementation because ensures type safety in both compile-time and runtime. I cannot argue with that. My comments, which are obviously biased, were about the simplicity of the expressions in every language and not about their implementation of the solution. Evidently, the designers of the language had the their reasons for this, but that is subject of another discussion and it was not my intention to go down that hole. – Edwin Dalorzo Mar 21 '12 at 17:41
7

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();
    }
svick
  • 236,525
  • 50
  • 385
  • 514
markmuetz
  • 9,334
  • 2
  • 32
  • 33
  • Well, if TSource is a child of TDest, I assume you should not need cast. Should you? This would be a classical upcasting. May I ask what is the ´this´ that appears in the parameters of the method? – Edwin Dalorzo Mar 21 '12 at 15:38
  • You're right, I've removed it in the answer. The 'this' turns a static method (in a static class) into an extension method, which means that it can be called like a method of `IList`. See [this link](http://msdn.microsoft.com/en-us/library/bb383977.aspx) for more on these. Note, there is also some type inference going on by the compiler so as it can work out what `TSource` and `TDest` are without you telling it explicitly. – markmuetz Mar 21 '12 at 16:03
  • Thanks a lot, your answer is pretty good. It is what I was looking for. Actually it can be implemented the same way in Java `public static

    void copy(List source, List

    destiny)` which, as you well pointed out, avoids the contravariance issue at all.

    – Edwin Dalorzo Mar 21 '12 at 16:09
4

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.

Paul Tyng
  • 7,924
  • 1
  • 33
  • 57
  • Are all convertible types expected to be convertible to decimal or can I expect an exception to happen at runtime if the condition is not met? – Edwin Dalorzo Mar 21 '12 at 14:31
  • To my knowledge all built in numeric types are, obviously no control over custom implementations, so exceptions are definitely possible. – Paul Tyng Mar 21 '12 at 14:34
  • This was a great answers, and it has been subject of interesting debate among my colleagues today here at the office. We are team of mixed developers: java and C# and you can image the holly wars that we sustain everyday over here :) – Edwin Dalorzo Mar 21 '12 at 23:35
2

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;
}
qxn
  • 17,162
  • 3
  • 49
  • 72
  • 2
    You could add the generic constraint using `sum(List numbers) where T : IConvertible` – Paul Tyng Mar 21 '12 at 14:23
  • You can't pass a `List` into this method though (you'll get an invalid conversion error). If you change the argument type to `IEnumerable`, and pass in e.g. `myInts.Cast()` it will work, but that requires casting the collection before it hits the method. – markmuetz Mar 21 '12 at 14:25
  • I see your point. Therefore, this cannot be statically checked to be a Number and I would get an exception of runtime if I try to covert something that is not compatible to double. – Edwin Dalorzo Mar 21 '12 at 14:30
  • @edalorzo -- See the update. The type T will now be checked at compile-time to implement IConvertible, which means it can be converted to a numeric. – qxn Mar 21 '12 at 14:34
  • +1 for the use of lambda expressions here to solve the problem. I have been looking forward to see those implemented in JDK 1.8 :) – Edwin Dalorzo Mar 21 '12 at 23:36