4

我有以下两种通用类型:

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”的定义,并且找不到接受“System.Collections.Generic.IEnumerable>”类型的第一个参数的扩展方法“Slice”(你是缺少 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>

这给了我同样的问题。

那么,有没有办法调整这个?

如果没有,我唯一的选择是:

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

2 回答 2

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,所以漂亮干净的扩展语法消失了(如果有一些约定允许你在扩展方法上获得类型推断,并且仍然允许类型参数的显式声明,无需重复可以推断的类型。

于 2009-12-21T01:14:49.830 回答
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> 并将原始扩展方法保留为采用“this IEnumerable> 范围”参数的静态类上的实际扩展方法。

对我来说,这个问题是基础接口(IRange)的智能感知行为与其后代不同 - 在基础接口上,您可以在不链接对 RangeExtensions 的调用的情况下看到扩展方法,而对于所有您必须调用 RangeExtensions 才能将其转换为后代接口。

我认为一致性比更新临时对象所获得的边际性能更重要。

让我知道你的想法 Lasse。

问候,菲尔

于 2009-12-21T06:16:32.727 回答