[英]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>
,只要有問題的R
是IRange<T>
。
所以我嘗試了這個:
public static Boolean Slice<R, T>(this IEnumerable<R> ranges)
where R : IRange<T>
where T : IComparable<T>
這給了我同樣的問題。
那么,有沒有辦法調整這個?
如果沒有,我唯一的選擇是:
以下是我設想定義這兩種方法的方法(注意,我還處於早期設計階段,所以這可能根本不起作用):
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.