Лассе, я добавляю другой ответ, поскольку он существенно отличается от моего существующего. (возможно, я не должен был делать это, и в этом случае, если кто-то даст мне знать, возможно, я смогу вместо этого включить его в существующий).
В любом случае, я придумала альтернативу, которая, на мой взгляд, довольно крутая и прямолинейная ...
Вместо того, чтобы заставлять дублировать каждый метод расширения из-за отсутствия со / контравариантности, предоставьте интерфейс свободно используемого типа, который маскирует требуемое поведение приведения. Преимущество этого заключается в том, что вам нужно предоставить только одну функцию для обработки приведения для всего набора методов расширения
Вот пример:
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 только один раз. Вам нужно только добавить один метод RangeExtensions для каждого потомка IRange, и поведение будет согласованным.
Есть также (как реализовано ниже) недостаток, заключающийся в том, что вы обновляете временный объект, поэтому существует (возможно, незначительное) снижение производительности.
В качестве альтернативы для каждого метода RangeExtensions можно было бы вместо этого возвращать IEnumerable> и оставлять исходные методы расширения в качестве реальных методов расширения для статического класса, принимающего аргументы «this IEnumerable> range».
Для меня проблема в том, что поведение intellisense для базового интерфейса (IRange) будет отличаться от его потомков - на базовом интерфейсе вы сможете увидеть методы расширения, не связывая вызов RangeExtensions, в то время как для всех дочерних интерфейсов вам придется вызывать RangeExtensions для его приведения.
Я думаю, что согласованность важнее, чем предельное снижение производительности, которое вы получите от обновления временного объекта.
Дайте мне знать, что вы думаете, Лассе.
С уважением,
Фил