簡體   English   中英

C#3中具有接口繼承(co(ntra) - 方差?)的通用類型推斷

[英]Generic type inference with interface inheritance (co(ntra)-variance?) in C# 3

我有以下兩種通用類型:

interface IRange<T> where T : IComparable<T>
interface IRange<T, TData> : IRange<T> where T : IComparable<T>
                           ^---------^
                                |
                                +- note: inherits from IRange<T>

現在我想為這些接口的集合定義一個擴展方法,因為它們都是IRange<T>或者來自IRange<T>我希望我能定義一個可以處理這兩個接口的方法。 注意,該方法不需要處理兩者之間的任何差異,只需要處理來自IRange<T>的公共部分。

我的問題是這樣的:

我可以定義一個擴展方法來處理這兩種類型中的任何一種的集合( IEnumerable<T> )嗎?

我試過這個:

public static void Slice<T>(this IEnumerable<IRange<T>> ranges)
    where T : IComparable<T>

但是,傳遞一個IEnumerable<IRange<Int32, String>> ,如下所示:

IEnumerable<IRange<Int32, String>> input = new IRange<Int32, String>[0];
input.Slice();

給我這個編譯器錯誤:

錯誤1'System.Collections.Generic.IEnumerable>'不包含'Slice'的定義,也沒有擴展方法'Slice'接受類型'System.Collections.Generic.IEnumerable>'的第一個參數可以找到(你是嗎?)缺少using指令或程序集引用?)C:\\ Dev \\ VS.NET \\ LVK \\ LVK.UnitTests \\ Core \\ Collections \\ RangeTests.cs 455 26 LVK.UnitTests

注意 :我沒想到它會編譯。 我知道co(ntra)-Variance(有一天我需要知道哪一個是哪種方式)才能知道這是行不通的。 我的問題是,我是否可以對Slice聲明做任何事情以使其工作。

好吧,那么我試着推斷范圍界面的類型,這樣我就可以處理所有類型的IEnumerable<R> ,只要有問題的RIRange<T>

所以我嘗試了這個:

public static Boolean Slice<R, T>(this IEnumerable<R> ranges)
    where R : IRange<T>
    where T : IComparable<T>

這給了我同樣的問題。

那么,有沒有辦法調整這個?

如果沒有,我唯一的選擇是:

  1. 定義兩個擴展方法,並在內部調用內部方法,也許通過將其中一個集合轉換為包含基本接口的集合?
  2. 等待C#4.0?

以下是我設想定義這兩種方法的方法(注意,我還處於早期設計階段,所以這可能根本不起作用):

public static void Slice<T>(this IEnumerable<IRange<T>> ranges)
    where T : IComparable<T>
{
    InternalSlice<T, IRange<T>>(ranges);
}

public static void Slice<T, TData>(this IEnumerable<IRange<T, TData>> ranges)
    where T : IComparable<T>
{
    InternalSlice<T, IRange<T, TData>>(ranges);
}

private static void Slice<T, R>(this IEnumerable<R> ranges)
    where R : IRange<T>
    where T : IComparable<T>

這是一個顯示我的問題的示例程序代碼。

請注意,通過在Main方法中將調用從Slice1更改為Slice2會使兩個用法產生編譯器錯誤,因此我的第二次嘗試甚至不處理我的初始情況。

using System;
using System.Collections.Generic;

namespace SO1936785
{
    interface IRange<T> where T : IComparable<T> { }
    interface IRange<T, TData> : IRange<T> where T : IComparable<T> { }

    static class Extensions
    {
        public static void Slice1<T>(this IEnumerable<IRange<T>> ranges)
            where T : IComparable<T>
        {
        }

        public static void Slice2<R, T>(this IEnumerable<R> ranges)
            where R : IRange<T>
            where T : IComparable<T>
        {
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<IRange<Int32>> a = new IRange<Int32>[0];
            a.Slice1();

            IEnumerable<IRange<Int32, String>> b = new IRange<Int32, String>[0];
            b.Slice1(); // doesn't compile, and Slice2 doesn't handle either
        }
    }
}

我認為你正確地回答了你自己的問題 - 沒有C#4.0對接口的共同/逆轉支持你不得不寫一些重復的代碼。

您可能還想使用IEnumerable<T> Enumerable.Cast<T>(this IEnumerable collection)方法 - 它的延遲執行,因此您可以在代碼中使用它(顯式)在T和T的子類之間進行轉換而無需創建一個新的集合。

雖然,您可能想要編寫自己的強制轉換,因為沒有約束可以確保集合包含T的后代,因此您對運行時異常持開放態度。 我想一個具有以下語法的函數可以工作,但是你將失去混合類型推理和擴展方法的能力:

public static IEnumerable<T> Cast<T,TSubset>(IEnumerable<TSubset> source)
   where TSubset : T
{
   foreach(T item in source) yield return item;
}

不幸的是,你必須指定T,因此干凈的擴展語法就走出了窗外(這將是很好,如果有一些公約,使你得到擴展方法類型推斷,並仍然允許的類型參數明確的聲明, 不必重復可以推斷出的類型。

Lasse,我正在添加另一個答案,因為這與我現有的答案大不相同。 (也許我不應該這樣做,在這種情況下,如果有人讓我知道,也許我可以把它合並到現有的)。

無論如何,我想出了一個替代方案,我認為它非常酷,而且很簡單......

由於缺乏共同/逆變而不是被迫復制每個擴展方法,而是提供了一個流暢的類型界面,它掩蓋了所需的鑄造行為。 這樣做的好處是,您只需提供一個函數來處理整個擴展方法集的轉換

這是一個例子:

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<IRange<int>> enumRange1 = new IRange<int>[0];
        IEnumerable<IRange<int, float>> enumRange2 = new IRange<int, float>[0];

        IEnumerable<IRange<int, float, string>> enumRange3 = new TestRange<int, float, string>[]
        {
            new TestRange<int, float, string> { Begin = 10, End = 20, Data = 3.0F, MoreData = "Hello" },
            new TestRange<int, float, string> { Begin = 5, End = 30, Data = 3.0F, MoreData = "There!" }
        };

        enumRange1.RangeExtensions().Slice();
        enumRange2.RangeExtensions().Slice();
        enumRange3.RangeExtensions().Slice();
    }
}

public interface IRange<T> where T : IComparable<T>
{
    int Begin { get; set; }
    int End { get; set; }
}

public interface IRange<T, TData> : IRange<T> where T : IComparable<T>
{
    TData Data { get; set; }
}

public interface IRange<T, TData, TMoreData> : IRange<T, TData> where T : IComparable<T>
{
    TMoreData MoreData { get; set; }
}

public class TestRange<T, TData, TMoreData> : IRange<T, TData, TMoreData>
    where T : IComparable<T>
{
    int m_begin;
    int m_end;
    TData m_data;
    TMoreData m_moreData;

    #region IRange<T,TData,TMoreData> Members
    public TMoreData MoreData
    {
        get { return m_moreData; }
        set { m_moreData = value; }
    }
    #endregion

    #region IRange<T,TData> Members
    public TData Data
    {
        get { return m_data; }
        set { m_data = value; }
    }
    #endregion

    #region IRange<T> Members
    public int Begin
    {
        get { return m_begin; }
        set { m_begin = value; }
    }

    public int End
    {
        get { return m_end; }
        set { m_end = value; }
    }
    #endregion
}

public static class RangeExtensionCasts
{
    public static RangeExtensions<T1> RangeExtensions<T1>(this IEnumerable<IRange<T1>> source)
        where T1 : IComparable<T1>
    {
        return new RangeExtensions<T1>(source);
    }

    public static RangeExtensions<T1> RangeExtensions<T1, T2>(this IEnumerable<IRange<T1, T2>> source)
        where T1 : IComparable<T1>
    {
        return Cast<T1, IRange<T1, T2>>(source);
    }

    public static RangeExtensions<T1> RangeExtensions<T1, T2, T3>(this IEnumerable<IRange<T1, T2, T3>> source)
        where T1 : IComparable<T1>
    {
        return Cast<T1, IRange<T1, T2, T3>>(source);
    }

    private static RangeExtensions<T1> Cast<T1, T2>(IEnumerable<T2> source)
        where T1 : IComparable<T1>
        where T2 : IRange<T1>
    {
        return new RangeExtensions<T1>(
            Enumerable.Select(source, (rangeDescendentItem) => (IRange<T1>)rangeDescendentItem));
    }
}

public class RangeExtensions<T>
    where T : IComparable<T>
{
    IEnumerable<IRange<T>> m_source;

    public RangeExtensions(IEnumerable<IRange<T>> source)
    {
        m_source = source;
    }

    public void Slice()
    {
        // your slice logic

        // to ensure the deferred execution Cast method is working, here I enumerate the collection
        foreach (IRange<T> range in m_source)
        {
            Console.WriteLine("Begin: {0} End: {1}", range.Begin, range.End);
        }
    }
}

當然缺點是使用'擴展方法'(它們不再是擴展方法)需要鏈接到對RangeExtensions方法的調用,但我認為這是一個相當不錯的權衡,因為無論它們有多少擴展方法現在可以在RangeExtensions類上提供一次。 您只需為IRange的每個后代添加一個RangeExtensions方法,並且行為是一致的。

還有(如下所示)你正在新建一個臨時對象的缺點,所以有一個(可能是邊際的)性能損失。

另一種方法是讓每個RangeExtensions方法返回一個IEnumerable>,並將原始擴展方法作為實際擴展方法保留在靜態類中,並采用'this IEnumerable> ranges'參數。

對我來說,問題在於基本接口(IRange)的intellisense行為與它的后代不同 - 在基本接口上,您可以看到擴展方法而無需鏈接對RangeExtensions的調用,而對於所有您必須調用RangeExtensions才能獲得它的后代接口。

我認為一致性比通過新建臨時對象獲得的邊際性能更重要。

讓我知道你對Lasse的看法。

問候,菲爾

暫無
暫無

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

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