9

これは、言語デザイン、パターン、およびセマンティクスについての難しい質問です。実用的な価値が見当たらないという理由だけで反対票を投じないでください。

まず、関数とそのパラメータについて考えてみましょう。次に、関数とそのパラメーター/引数と、ジェネリッククラス/関数とその型パラメーター/型引数との類似点を見ていきます。

関数は、「パラメータ」と呼ばれるいくつかの不特定の値を持つコードのブロックです。引数を指定して結果を受け取ります。

ジェネリッククラスは、いくつかの不特定の「タイプパラメータ」を持つクラスです。type -argumentsを指定すると、クラスを操作できます。コンストラクターを呼び出すか、静的メソッドを呼び出します。

非ジェネリッククラスのジェネリック関数は、いくつかの不特定の「型パラメーター」といくつかの不特定の「パラメーター」を持つ関数です。結果を受け取るために、型引数値引数を指定します。

デリゲートは、特定の機能へのポインターです。デリゲートを作成するときは、関数の引数を指定せず、後で指定します。

問題は、.Netには、ジェネリック型パラメーターが指定されていないジェネリック関数のデリゲートに相当するものがないことです。後でtype-parametersにtype-valuesを指定することはできません。自由値パラメーターだけでなく、自由型パラメーターも持つデリゲートを想像することができます。

static class SomeClass {
    //generic function
    public static T GetValue<T>() {
        return default(T);
    }
}

//creating delegate to generic function or method group
Func{TFree}<TFree> valueFactory = SomeClass.GetValue;

//creating delegate to anonymous generic function
Func{TFree}<int, List<TFree>> listFactory = {TFree}(int capacity) => new List<TFree>(capacity);

以下は、C#で記述したいプログラムの[擬似]コードです。正しいC#プログラムで同様の動作を実現する方法を知りたいです。

C#で無料のジェネリック型パラメーターを使用してデリゲートをエミュレートするにはどうすればよいですか?

非ジェネリックコードを介して、まだ未知のジェネリックパラメータを持つジェネリック関数への参照/リンクをどのように渡すことができますか?

public static class Factory { //Everything compiles fine here
    public delegate ICollection<T> FactoryDelegate<T>(IEnumerable<T> values);

    public static ICollection<T> CreateList<T>(IEnumerable<T> values) {
        return new List<T>(values);
    }

    public static ICollection<T> CreateSet<T>(IEnumerable<T> values) {
        return new HashSet<T>(values);
    }
}

public class Worker { //non-generic class
    Func{TFree}<FactoryDelegate<TFree>> _factory; //TFree is a "free" generic type paramenter

    public Worker(Func{TFree}<FactoryDelegate<TFree>> factory) {
        _factory = factory;
    }

    public ICollection<T> DoWork<T>(IEnumerable<T> values) { //generic method
        return _factory{T}(values); //supplying T as the argument for type parameter TFree
    }
}

public static class Program {
    public static void Main() {
        string[] values1 = new string[] { "a", "b", "c" };
        int[] values2 = new int[] { 1, 2, 2, 2 };

        Worker listWorker = new Worker(Factory.CreateList); //passing reference to generic function
        Worker setWorker = new Worker(Factory.CreateSet); //passing reference to generic function

        ICollection<string> result1 = listWorker.DoWork(values1);
        ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4
        ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2
    }
}

型引数を指定せずに、ジェネリック関数(Factory.CreateListおよびFactory.CreateSet)への参照をWorkerクラスコンストラクターに渡す方法をご覧ください。型引数は、後で汎用DoWork関数が具象型配列で呼び出されたときに提供されます。DoWorkは、type-argumentsを使用して正しい関数を選択し、value-argumentsをそれに渡し、受け取った値を返します。

最終的な解決策: C#で無料のジェネリック型パラメーターを使用してデリゲートをエミュレートする

4

3 に答える 3

9

これを言語でエミュレートする方法は、デリゲートではなくインターフェイスを使用することだと思います。非ジェネリックインターフェイスにはジェネリックメソッドを含めることができるため、オープン型引数を使用してデリゲートの動作のほとんどを取得できます。

有効なC#プログラムに作り直された例を次に示します(定義したFactoryクラスが引き続き必要であることに注意してください)。

public interface IWorker
{
    ICollection<T> DoWork<T>(IEnumerable<T> values);
}

public class ListCreationWorker : IWorker
{
    public ICollection<T> DoWork<T>(IEnumerable<T> values)
    {
        return Factory.CreateList<T>(values);
    }
}

public class SetCreationWorker : IWorker
{
    public ICollection<T> DoWork<T>(IEnumerable<T> values)
    {
        return Factory.CreateSet<T>(values);  
    }
}

public static class Program {
    public static void Main(string[] args) {
        string[] values1 = new string[] { "a", "b", "c" };
        int[] values2 = new int[] { 1, 2, 2, 2 };

        IWorker listWorker = new ListCreationWorker();
        IWorker setWorker = new SetCreationWorker();

        ICollection<string> result1 = listWorker.DoWork(values1);
        ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4
        ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2
    }
}

public static class Factory
{
    public static ICollection<T> CreateSet<T>(IEnumerable<T> values)
    {
        return new HashSet<T>(values);
    }

    public static ICollection<T> CreateList<T>(IEnumerable<T> values)
    {
        return new List<T>(values);
    }
}

どのメソッドを呼び出すかの決定を、そのメソッドの実行から分離するという重要な機能を引き続き利用できます。

ただし、実行できないことの1つは、IWorker実装に一般的な方法で状態を格納することです。DoWorkメソッドは毎回異なる型の引数で呼び出される可能性があるため、これがどのように役立つかはわかりません。

于 2012-10-14T03:40:48.290 に答える
2

これは、.Netの型システムでは実際には意味がありません。

あなたが説明しているのは、型コンストラクターです。これは、 1つ以上の型を取り、具体的な(パラメーター化された、または閉じた)型を返す「関数」です。

問題は、型構築子自体が型ではないということです。オープンタイプのオブジェクトまたは変数を持つことはできません。型コンストラクターは、具象型を生成するためにのみ使用できます。

言い換えると、.Netの型システム内で開いている関数への参照を表す方法はありません。


できる最善のことは、リフレクションを使用することです。aMethodInfoはオープンジェネリックメソッドを記述できます。偽のジェネリックパラメーターを持つ式ツリーを受け取るジェネリックメソッドを作成する
ことで、コンパイル時の型セーフなopenへの参照を取得できます。MethodInfo

public MethodInfo GetMethod<TPlaceholder>(Expression<Action> method) {
    //Find the MethodInfo and remove all TPlaceholder parameters
}

GetMethod<string>(() => SomeMethod<string>(...));

このTPlaceholderパラメーターは、そのパラメーターに制約があるオープンジェネリックメソッドを参照する場合に必要です。制約を満たすプレースホルダータイプを選択できます。

于 2012-10-14T02:53:58.050 に答える
2

解決策はインターフェースです。@ mike-zが書いたように、インターフェースはジェネリックメソッドをサポートします。したがって、あるクラスのジェネリックメソッドへの参照をカプセル化するジェネリックメソッドを使用して、非ジェネリックインターフェイスIFactoryを作成できます。このようなインターフェイスを使用して[Factory]クラスのジェネリックメソッドをバインドするには、通常、IFactoryインターフェイスを実装する小さなクラスを作成する必要があります。それらは、ラムダによって使用されるクロージャーのように機能します。

これと、私が要求した一般的なメソッドデリゲートとの間に大きなセマンティックディファレンスは見られません。このソリューションは、コンパイラがラムダに対して行うものと非常によく似ています[他のメソッドを呼び出すだけです](を呼び出すメソッドでクロージャを作成します)。

私たちは何を失っていますか?主に糖衣構文。

  • 匿名関数/ラムダ。ジェネリックラムダを作成することはできません。(Javaのように)匿名クラスを作成できれば、問題は解決します。しかし、ラムダは.Netの単なる構文糖衣であるため、これはそもそもそれほど問題ではありません。

  • メソッドグループ(C#用語)からデリゲート/リンクを暗黙的に作成する機能。汎用である場合、メソッドグループを使用することはできません。これはセマンティクスにも影響しません。

  • ジェネリックデリゲートを定義する機能が妨げられます。IFactory<U, V>メソッドを使用してジェネリックインターフェイスを作成することはできませんV<T> Create<T>(U<T> arg)。これも問題ではありません。

これがソリューションのコードです。質問のFactoryクラスは変更されていません。

public interface IFactory {
    ICollection<T> Create<T>(IEnumerable<T> values);
}

public class Worker { //not generic
    IFactory _factory;

    public Worker(IFactory factory) {
        _factory = factory;
    }

    public ICollection<T> DoWork<T>(IEnumerable<T> values) { //generic method
        return _factory.Create<T>(values);
    }
}

public static class Program {
    class ListFactory : IFactory {
        public ICollection<T> Create<T>(IEnumerable<T> values) {
            return Factory.CreateList(values);
        }
    }

    class SetFactory : IFactory {
        public ICollection<T> Create<T>(IEnumerable<T> values) {
            return Factory.CreateSet(values);
        }
    }

    public static void Main() {
        string[] values1 = new string[] { "a", "b", "c" };
        int[] values2 = new int[] { 1, 2, 2, 2 };

        Worker listWorker = new Worker(new ListFactory());
        Worker setWorker = new Worker(new SetFactory());

        ICollection<string> result1 = listWorker.DoWork(values1);
        ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4
        ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2
    }
}
于 2012-10-17T04:09:38.790 に答える