C#中的协方差和逆变

时间:2012-03-21 14:02:52

标签: c# java covariance contravariance

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

我一直在使用C#generics几个小时,我已经能够在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#中的两个不同接口来完成。

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

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

4 个答案:

答案 0 :(得分:24)

不是直接回答你的问题,而是回答一些稍微不同的问题:

  

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

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

  

Java的“呼叫站点”协方差/逆变与C#的“声明站点”协方差/逆变之间有什么区别?

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

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

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

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

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

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

相比之下,Java在本地网站上说,我们可以协同处理这种类型,我们保证不会在这里调用任何可能违反类型安全的方法。

我没有足够的Java使用经验来说明在哪种情况下“更好”。 Java技术当然很有趣。

答案 1 :(得分:7)

对于你问题的第二部分,你不需要逆变,你需要做的就是说第一种类型可以转换为第二种。同样,使用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();
    }

答案 2 :(得分:4)

您可以使用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

答案 3 :(得分:2)

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

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

<强>更新

在这种情况下,我个人使用IEnumerable和泛型类型(并且,由于 Paul Tyng ,您可以强制T实施{ {1}}):

IConvertible