簡體   English   中英

C#中的協方差和逆變

[英]Covariance and Contravariance in C#

我將首先說我是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#中的兩個不同接口完成。

有關如何實現這一點的任何想法?

非常感謝大家提供的任何答案。 我很確定我會從你能提供的任何例子中學到很多東西。

而不是直接回答你的問題,我將回答一些略有不同的問題:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM