4


1

C#3のインターフェイス継承(co(ntra)-variance?)を使用したジェネリック型推論

次の2つの汎用タイプがあります。

interface IRange where T : IComparable
interface IRange : IRange where T : IComparable
                           ^---------^
                                |
                                +- note: inherits from IRange

次に、これらのインターフェイスのコレクションの拡張メソッドを定義します。これらは両方とも「IRange」または「IRange」から派生したものであるため、両方を処理する1つのメソッドを定義できると期待していました。 このメソッドは2つの違いを処理する必要はなく、 `IRange`の共通部分のみを処理する必要があることに注意してください。

したがって、私の質問はこれです:

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

私はこれを試しました:

public static void Slice(this IEnumerable> ranges)
    where T : IComparable

ただし、次のように、 `IEnumerable>`を渡します。

IEnumerable> input = new IRange[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宣言に対してできることは何かありますか。

それでは、問題の「R」が「IRange」である限り、すべてのタイプの「IEnumerable」を処理できるように、範囲インターフェースのタイプを推測しようとしました。

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

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

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

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

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

  1. 2つの拡張メソッドを定義し、内部メソッドを内部的に呼び出します。 おそらく、コレクションの1つをベースインターフェイスを含むコレクションに変換することにより、

  2. C#4.0を待ちますか?

'' '' '

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

public static void Slice(this IEnumerable> ranges)
    where T : IComparable
{
    InternalSlice>(ranges);
}

public static void Slice(this IEnumerable> ranges)
    where T : IComparable
{
    InternalSlice>(ranges);
}

private static void Slice(this IEnumerable ranges)
    where R : IRange
    where T : IComparable

'' '' '

ここに私の問題を示すサンプルプログラムコードがあります。

MainメソッドでSlice1からSlice2への呼び出しを変更すると、両方の使用法でコンパイラエラーが発生するため、2回目の試行でも最初のケースを処理できませんでした。

using System;
using System.Collections.Generic;

namespace SO1936785
{
    interface IRange where T : IComparable { }
    interface IRange : IRange where T : IComparable { }

    static class Extensions
    {
        public static void Slice1(this IEnumerable> ranges)
            where T : IComparable
        {
        }

        public static void Slice2(this IEnumerable ranges)
            where R : IRange
            where T : IComparable
        {
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable> a = new IRange[0];
            a.Slice1();

            IEnumerable> b = new IRange[0];
            b.Slice1(); // doesn't compile, and Slice2 doesn't handle either
        }
    }
}

2 Answer


2


あなたはあなた自身の質問に正しく答えたと思います-インターフェイスのC#4.0の共/共分散のサポートなしでは、重複コードを書くことを余儀なくされます。

また、 `IEnumerable Enumerable.Cast(this IEnumerable collection)`メソッドを使用することもできます。実行されるのは遅延であるため、コード内で(明示的に)使用して、新しいコレクションを作成せずにTとTのサブクラス間で変換できます。

ただし、コレクションにTの子孫が含まれることを保証する制約がないため、独自のキャストを作成することもできます。したがって、ランタイム例外が発生します。 次の構文の関数は機能すると思いますが、型推論と拡張メソッドを混在させる機能は失われます。

public static IEnumerable Cast(IEnumerable source)
   where TSubset : T
{
   foreach(T item in source) yield return item;
}

残念なことに、Tを指定する必要があるため、きれいな拡張構文がウィンドウから消えてしまいます(拡張メソッドで型推論を取得でき、なおかつ型引数の明示的なステートメントを許可する何らかの規則がある場合は良いでしょう推測できるタイプを繰り返します。


1


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

とにかく、私はかなりクールで簡単だと思う代替案を思い付きました…​

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

これが一例です。

class Program
{
    static void Main(string[] args)
    {
        IEnumerable> enumRange1 = new IRange[0];
        IEnumerable> enumRange2 = new IRange[0];

        IEnumerable> enumRange3 = new TestRange[]
        {
            new TestRange { Begin = 10, End = 20, Data = 3.0F, MoreData = "Hello" },
            new TestRange { Begin = 5, End = 30, Data = 3.0F, MoreData = "There!" }
        };

        enumRange1.RangeExtensions().Slice();
        enumRange2.RangeExtensions().Slice();
        enumRange3.RangeExtensions().Slice();
    }
}

public interface IRange where T : IComparable
{
    int Begin { get; set; }
    int End { get; set; }
}

public interface IRange : IRange where T : IComparable
{
    TData Data { get; set; }
}

public interface IRange : IRange where T : IComparable
{
    TMoreData MoreData { get; set; }
}

public class TestRange : IRange
    where T : IComparable
{
    int m_begin;
    int m_end;
    TData m_data;
    TMoreData m_moreData;

    #region IRange Members
    public TMoreData MoreData
    {
        get { return m_moreData; }
        set { m_moreData = value; }
    }
    #endregion

    #region IRange Members
    public TData Data
    {
        get { return m_data; }
        set { m_data = value; }
    }
    #endregion

    #region IRange 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 RangeExtensions(this IEnumerable> source)
        where T1 : IComparable
    {
        return new RangeExtensions(source);
    }

    public static RangeExtensions RangeExtensions(this IEnumerable> source)
        where T1 : IComparable
    {
        return Cast>(source);
    }

    public static RangeExtensions RangeExtensions(this IEnumerable> source)
        where T1 : IComparable
    {
        return Cast>(source);
    }

    private static RangeExtensions Cast(IEnumerable source)
        where T1 : IComparable
        where T2 : IRange
    {
        return new RangeExtensions(
            Enumerable.Select(source, (rangeDescendentItem) => (IRange)rangeDescendentItem));
    }
}

public class RangeExtensions
    where T : IComparable
{
    IEnumerable> m_source;

    public RangeExtensions(IEnumerable> 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 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を呼び出してキャストする必要があります。

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

あなたがラッセをどう思うか教えてください。

よろしく、Phil