C#関数を辞書に保存する


93

関数を格納できる辞書を作成するにはどうすればよいですか?

ありがとう。

ユーザーから実行できる約30以上の機能があります。この方法で関数を実行できるようにしたい:

   private void functionName(arg1, arg2, arg3)
   {
       // code
   }

   dictionaryName.add("doSomething", functionName);

    private void interceptCommand(string command)
    {
        foreach ( var cmd in dictionaryName )
        {
            if ( cmd.Key.Equals(command) )
            {
                cmd.Value.Invoke();
            }
        }
    }

ただし、関数のシグネチャは常に同じではないため、引数の数が異なります。


5
これは素晴らしいイディオムです-厄介なswitchステートメントを置き換えることができます。
Hamish Grubijan、2010年

サンプル関数にはパラメーターがありますが、ストアード関数を呼び出すときは、引数なしで呼び出します。関数が保存されるときに引数は固定されますか?
Tim Lloyd

1
@HamishGrubijan、これをswitchステートメントを置き換えるために実行すると、コンパイル時の最適化と明確性がすべて破棄されます。だから、私はそれがイディオムよりもバカだったと思います。実行時に変化する可能性のある方法で機能を動的にマッピングする場合は、役立つ場合があります。
Jodrell 2013

12
@ジョドレル、A)馬鹿は建設的ではない強力な言葉です。B)明快さは見る人の目にあります。醜いswitch文をたくさん見ました。C)コンパイル時間の最適化...このスレッドのZoobaは、反対のstackoverflow.com/questions/505454/…を主張します。switch ステートメントがO(log N)の場合、違いを生むために速度を上げるには100以上のケースを含める必要があります。 。もちろん、それは判読できません。たぶん、switchステートメントは完全なハッシュ関数を利用できますが、それは少数のケースに限られます。.Netを使用している場合は、マイクロ秒を超えて心配する必要はありません。
Hamish Grubijan 2013

4
@Jodrell、(続き)ハードウェアを最大限に活用したい場合は、ASM、C、またはC ++をこの順序で使用します。.Netでの辞書検索はあなたを殺しません。デリゲートのさまざまなシグネチャ-これは、シンプルな辞書アプローチを使用して作成した最も強力なポイントです。
Hamish Grubijan 2013

回答:


120

このような:

Dictionary<int, Func<string, bool>>

これにより、文字列パラメータを取り、ブール値を返す関数を保存できます。

dico[5] = foo => foo == "Bar";

または、関数が匿名でない場合:

dico[5] = Foo;

Fooは次のように定義されています。

public bool Foo(string bar)
{
    ...
}

更新:

更新を確認した後、呼び出す関数のシグネチャが事前にわからないようです。.NETでは、関数を呼び出すためにすべての引数を渡す必要があります。引数が何であるかわからない場合は、これを実現するための唯一の方法はリフレクションです。

そして、もう一つの選択肢があります:

class Program
{
    static void Main()
    {
        // store
        var dico = new Dictionary<int, Delegate>();
        dico[1] = new Func<int, int, int>(Func1);
        dico[2] = new Func<int, int, int, int>(Func2);

        // and later invoke
        var res = dico[1].DynamicInvoke(1, 2);
        Console.WriteLine(res);
        var res2 = dico[2].DynamicInvoke(1, 2, 3);
        Console.WriteLine(res2);
    }

    public static int Func1(int arg1, int arg2)
    {
        return arg1 + arg2;
    }

    public static int Func2(int arg1, int arg2, int arg3)
    {
        return arg1 + arg2 + arg3;
    }
}

このアプローチでも、辞書の対応するインデックスで各関数に渡す必要があるパラメーターの数とタイプを知る必要があります。そうしないと、ランタイムエラーが発生します。また、関数に戻り値がない場合は、のSystem.Action<>代わりに使用してくださいSystem.Func<>


そうですか。そのとき、私は反射について読むのに少し時間を費やす必要があると思います。そして、助けに感謝します。
カイ

@chi、私の最新のアップデートを見てください。で例を追加しましたDictionary<int, Delegate>
Darin Dimitrov

3
私は反対票を投じませんが、この種の実装は非常に正当な理由なしアンチパターンであると言う必要があります。OPがクライアントがすべての引数を関数に渡すことを期待する場合、なぜ最初に関数テーブルがあるのでしょうか。なぜ、クライアントは単に持っていない呼び出すダイナミック呼び出すの魔法ずに機能を?
ジュリエット

1
@ジュリエット、私はあなたに同意します、それが良いことだとは決して言いませんでした。ところで、私の回答では、辞書の対応するインデックスで各関数に渡す必要があるパラメーターの数と型を知る必要があることを強調しました。この場合、関数を直接呼び出すことができるので、おそらくハッシュテーブルも必要ないことを指摘することで、あなたは間違いなく正しいです。
Darin Dimitrov

1
引数をとらず、戻り値のない関数を格納する必要がある場合はどうなりますか?
-DataGreed

8

ただし、関数のシグネチャは常に同じではないため、引数の数が異なります。

このように定義されたいくつかの関数から始めましょう:

private object Function1() { return null; }
private object Function2(object arg1) { return null; }
private object Function3(object arg1, object arg3) { return null; }

あなたは本当にあなたの処分で2つの実行可能なオプションを持っています:

1)クライアントに関数を直接呼び出させることにより、タイプセーフを維持します。

あなたが持っていない限りこれは、おそらく最高のソリューションです非常にこのモデルから壊すための十分な理由を。

関数呼び出しをインターセプトしたいという話をすると、仮想関数を再発明しようとしているように聞こえます。基本クラスからの継承や関数のオーバーライドなど、この種の機能をすぐに利用できる方法はたくさんあります。

それはあなたがより多くのだクラスたいように私に聞こえるラッパー基本クラスの派生インスタンスよりは、ので、このような何かを実行します。

public interface IMyObject
{
    object Function1();
    object Function2(object arg1);
    object Function3(object arg1, object arg2);
}

class MyObject : IMyObject
{
    public object Function1() { return null; }
    public object Function2(object arg1) { return null; }
    public object Function3(object arg1, object arg2) { return null; }
}

class MyObjectInterceptor : IMyObject
{
    readonly IMyObject MyObject;

    public MyObjectInterceptor()
        : this(new MyObject())
    {
    }

    public MyObjectInterceptor(IMyObject myObject)
    {
        MyObject = myObject;
    }

    public object Function1()
    {
        Console.WriteLine("Intercepted Function1");
        return MyObject.Function1();
    }
    public object Function2(object arg1)
    {
        Console.WriteLine("Intercepted Function2");
        return MyObject.Function2(arg1);
    }

    public object Function3(object arg1, object arg2)
    {
        Console.WriteLine("Intercepted Function3");
        return MyObject.Function3(arg1, arg2);
    }
}

2)または、関数の入力を共通のインターフェースにマッピングします。

これは、すべての関数が関連している場合に機能する可能性があります。たとえば、ゲームを作成しているときに、すべての関数がプレーヤーの一部またはプレーヤーのインベントリに対して何らかの処理を行っているとします。あなたはこのようなものになるでしょう:

class Interceptor
{
    private object function1() { return null; }
    private object function2(object arg1) { return null; }
    private object function3(object arg1, object arg3) { return null; }

    Dictionary<string, Func<State, object>> functions;

    public Interceptor()
    {
        functions = new Dictionary<string, Func<State, object>>();
        functions.Add("function1", state => function1());
        functions.Add("function2", state => function2(state.arg1, state.arg2));
        functions.Add("function3", state => function3(state.arg1, state.are2, state.arg3));
    }

    public object Invoke(string key, object state)
    {
        Func<object, object> func = functions[key];
        return func(state);
    }
}

私の仮定はobject、新しいスレッドに渡されるようなものです。
Scott Fraley 2017年

1

ちょっと、これが役に立てば幸いです。何語から来たの?

internal class ForExample
{
    void DoItLikeThis()
    {
        var provider = new StringMethodProvider();
        provider.Register("doSomethingAndGetGuid", args => DoSomeActionWithStringToGetGuid((string)args[0]));
        provider.Register("thenUseItForSomething", args => DoSomeActionWithAGuid((Guid)args[0],(bool)args[1]));


        Guid guid = provider.Intercept<Guid>("doSomethingAndGetGuid", "I don't matter except if I am null");
        bool isEmpty = guid == default(Guid);
        provider.Intercept("thenUseItForSomething", guid, isEmpty);
    }

    private void DoSomeActionWithAGuid(Guid id, bool isEmpty)
    {
        // code
    }

    private Guid DoSomeActionWithStringToGetGuid(string arg1)
    {
        if(arg1 == null)
        {
            return default(Guid);
        }
        return Guid.NewGuid();
    }

}
public class StringMethodProvider
{
    private readonly Dictionary<string, Func<object[], object>> _dictionary = new Dictionary<string, Func<object[], object>>();
    public void Register<T>(string command, Func<object[],T> function)
    {
        _dictionary.Add(command, args => function(args));
    }
    public void Register(string command, Action<object[]> function)
    {
        _dictionary.Add(command, args =>
                                     {
                                         function.Invoke(args);
                                         return null;
                                     } );
    }
    public T Intercept<T>(string command, params object[] args)
    {
        return (T)_dictionary[command].Invoke(args);
    }
    public void Intercept(string command, params object[] args)
    {
        _dictionary[command].Invoke(args);
    }
}

1

params object[] listメソッドパラメータに使用せず、メソッド(または呼び出しロジック)内で検証を行います。これにより、パラメータの数を変更できます。


1

次のシナリオでは、要素の辞書を使用して入力パラメーターとして送信し、出力パラメーターと同じものを取得できます。

最初に次の行を上部に追加します。

using TFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Collections.Generic.IDictionary<string, object>>;

次に、クラス内で、次のように辞書を定義します。

     private Dictionary<String, TFunc> actions = new Dictionary<String, TFunc>(){

                        {"getmultipledata", (input) => 
                            {
                                //DO WORKING HERE
                                return null;
                            } 
                         }, 
                         {"runproc", (input) => 
                            {
                                //DO WORKING HERE
                                return null;
                            } 
                         }
 };

これにより、次のような構文でこれらの匿名関数を実行できます。

var output = actions["runproc"](inputparam);

0

辞書を定義System.Actionし、タイプとして使用して、関数参照を値として追加します。

using System.Collections;
using System.Collections.Generic;

public class Actions {

    public Dictionary<string, System.Action> myActions = new Dictionary<string, System.Action>();

    public Actions() {
        myActions ["myKey"] = TheFunction;
    }

    public void TheFunction() {
        // your logic here
    }
}

それからそれを呼び出します:

Actions.myActions["myKey"]();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.