4

次の 2 つのジェネリック型があります。

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>、両方を処理する 1 つのメソッドを定義できることを望んでいました。このメソッドは、2 つの違いを処理する必要はなく、 からの共通部分のみを処理する必要があることに注意してくださいIRange<T>

したがって、私の質問は次のとおりです。

IEnumerable<T>これら 2 つのタイプのいずれかのコレクション ( ) を処理する拡張メソッドを 1 つ定義できますか?

私はこれを試しました:

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>ましRIRange<T>

だから私はこれを試しました:

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

これは私に同じ問題を与えます。

それで、これを微調整する方法はありますか?

そうでない場合、私の唯一のオプションは次のとおりです。

  1. 2 つの拡張メソッドを定義し、おそらくコレクションの 1 つを基本インターフェイスを含むコレクションに変換することによって、内部メソッドを内部的に呼び出しますか?
  2. C# 4.0 を待ちますか?

2 つのメソッドを定義する方法を以下に示します (注: まだ設計の初期段階にあるため、まったく機能しない可能性があります)。

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

ラッセ、これは私の既存のものとは実質的に異なるので、私は別の答えを追加しています。(多分私はこれをするべきではありません、その場合誰かが私に多分私に知らせたなら私は代わりにそれを既存のものに組み込むことができます)。

とにかく、私はかなりクールで簡単だと思う代替案を考え出しました...

共変性/反変性がないために各拡張メソッドを複製することを余儀なくされる代わりに、必要なキャスト動作をマスクする流暢なタイプのインターフェイスを提供します。これには、拡張メソッドのセット全体のキャストを処理するために1つの関数を提供するだけでよいという利点があります。

次に例を示します。

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の子孫ごとに1つのRangeExtensionsメソッドを追加するだけで、動作は一貫しています。

(以下に実装されているように)一時オブジェクトを更新するという欠点もあるため、(おそらくわずかな)パフォーマンスの低下があります。

別の方法は、各RangeExtensionsメソッドが代わりにIEnumerable>を返し、元の拡張メソッドを静的クラスの実際の拡張メソッドとして「このIEnumerable>範囲」引数を取ることです。

私にとって、これに関する問題は、インテリセンスの動作がベースインターフェイス(IRange)とその子孫で異なることです。ベースインターフェイスでは、RangeExtensionsへの呼び出しをチェーンせずに拡張メソッドを表示できますが、子孫インターフェイスをキャストするには、RangeExtensionsを呼び出す必要があります。

一時オブジェクトを更新することで得られる限界パフォーマンスヒットよりも、一貫性の方が重要だと思います。

ラッセの感想を教えてください。

よろしく、フィル

于 2009-12-21T06:16:32.727 に答える