我有以下两种通用类型:
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&gt;'不包含'Slice'的定义,也没有扩展方法'Slice'接受'System.Collections.Generic.IEnumerable&gt;'类型的第一个参数可以找到(您是否缺少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
}
}
}
答案 0 :(得分:2)
我认为你正确地回答了你自己的问题 - 没有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,所以漂亮的干净扩展语法会超出窗口(如果有一些约定允许你对扩展方法进行类型推断,并且仍然允许显式类型参数声明,那将会很好,没有必须重复可以推断的类型。
答案 1 :(得分:1)
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&gt;并将原始扩展方法作为实际扩展方法保留在静态类中,并采用'this IEnumerable&gt;范围的论点。
对我来说,问题在于基本接口(IRange)的intellisense行为与它的后代不同 - 在基本接口上,您可以看到扩展方法而无需链接对RangeExtensions的调用,而对于所有后代接口,您必须调用RangeExtensions来获取它。
我认为一致性比新近临时对象所获得的边际性能更重要。
让我知道你对Lasse的看法。
此致 菲尔