C # Menyimpan fungsi dalam Kamus

93

Bagaimana cara membuat Kamus tempat saya dapat menyimpan fungsi?

Terima kasih.

Saya memiliki sekitar 30+ fungsi yang dapat dijalankan dari pengguna. Saya ingin dapat menjalankan fungsi dengan cara ini:

   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();
            }
        }
    }

Namun, tanda tangan fungsi tidak selalu sama, sehingga memiliki jumlah argumen yang berbeda.

chi
sumber
5
Ini adalah idiom yang bagus - dapat menggantikan pernyataan switch yang buruk.
Hamish Grubijan
Fungsi contoh Anda memiliki parameter, namun, saat Anda menjalankan fungsi tersimpan, Anda menjalankannya tanpa argumen apa pun. Apakah argumen diperbaiki saat fungsi disimpan?
Tim Lloyd
1
@HamishGrubijan, jika Anda melakukan ini untuk mengganti pernyataan switch maka Anda membuang semua optimasi dan kejelasan waktu kompilasi. Jadi, menurutku itu lebih idiot daripada idiom. Jika Anda ingin memetakan fungsionalitas secara dinamis dengan cara yang dapat bervariasi pada waktu proses, ini mungkin berguna.
Jodrell
12
@Jodrell, A) Idiot adalah kata yang kuat yang tidak konstruktif, B) Kejelasan ada di mata orang yang melihatnya. Saya telah melihat banyak pernyataan switch yang jelek. C) Pengoptimalan waktu kompilasi ... Zooba di utas ini berpendapat sebaliknya stackoverflow.com/questions/505454/… Jika pernyataan sakelar adalah O (log N), maka itu harus berisi 100+ kasus agar kecepatan dapat membuat perbedaan . Itu tidak bisa dibaca, tentu saja. Mungkin pernyataan switch dapat menggunakan fungsi hash yang sempurna, tetapi hanya untuk # kecil kasus. Jika Anda menggunakan .Net, maka Anda tidak perlu khawatir selama mikrodetik.
Hamish Grubijan
4
@Jodrell, (lanjutan) Jika Anda ingin memaksimalkan perangkat keras Anda, gunakan ASM, C, atau C ++ dalam urutan itu. Pencarian kamus di .Net tidak akan membunuh Anda. Berbagai tanda tangan dari para delegasi - itu adalah poin terkuat yang Anda buat terhadap penggunaan pendekatan kamus sederhana.
Hamish Grubijan

Jawaban:

121

Seperti ini:

Dictionary<int, Func<string, bool>>

Ini memungkinkan Anda untuk menyimpan fungsi yang mengambil parameter string dan mengembalikan boolean.

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

Atau jika fungsinya tidak anonim:

dico[5] = Foo;

dimana Foo didefinisikan seperti ini:

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

MEMPERBARUI:

Setelah melihat pembaruan Anda, tampaknya Anda tidak tahu sebelumnya tanda tangan dari fungsi yang ingin Anda panggil. Dalam .NET untuk menjalankan fungsi, Anda harus meneruskan semua argumen dan jika Anda tidak tahu argumen apa yang akan menjadi satu-satunya cara untuk mencapai ini adalah melalui refleksi.

Dan inilah alternatif lain:

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

Dengan pendekatan ini, Anda masih perlu mengetahui jumlah dan jenis parameter yang perlu diteruskan ke setiap fungsi pada indeks kamus yang sesuai atau Anda akan mendapatkan error runtime. Dan jika fungsi Anda tidak memiliki nilai kembali menggunakan System.Action<>bukan System.Func<>.

Darin Dimitrov
sumber
Saya melihat. Saya rasa saya harus meluangkan waktu untuk membaca tentang refleksi, kemudian hargai bantuannya.
chi
@chi, lihat pembaruan terakhir saya. Saya telah menambahkan contoh dengan Dictionary<int, Delegate>.
Darin Dimitrov
3
Saya tidak akan downvote, tapi saya hanya perlu mengatakan implementasi semacam ini adalah antipattern tanpa alasan yang sangat bagus. Jika OP mengharapkan klien untuk meneruskan semua argumen ke fungsi, lalu mengapa memiliki tabel fungsi di tempat pertama? Mengapa tidak meminta klien hanya memanggil fungsi tanpa keajaiban pemanggilan dinamis?
Juliet
1
@ Juliet, saya setuju dengan Anda, saya tidak pernah mengatakan itu hal yang baik. By the way dalam jawaban saya, saya menekankan pada fakta bahwa kita masih perlu mengetahui jumlah dan jenis parameter yang perlu diteruskan ke setiap fungsi pada indeks kamus yang sesuai. Anda benar sekali dengan menunjukkan bahwa dalam kasus ini kita mungkin bahkan tidak memerlukan hashtable karena kita bisa langsung memanggil fungsinya.
Darin Dimitrov
1
Bagaimana jika saya perlu menyimpan fungsi yang tidak mengambil argumen dan tidak memiliki nilai kembali?
DataGreed
8

Namun, tanda tangan fungsi tidak selalu sama, sehingga memiliki jumlah argumen yang berbeda.

Mari kita mulai dengan beberapa fungsi yang didefinisikan seperti ini:

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

Anda benar-benar memiliki 2 opsi yang dapat Anda gunakan:

1) Pertahankan keamanan tipe dengan meminta klien memanggil fungsi Anda secara langsung.

Ini mungkin solusi terbaik, kecuali Anda memiliki alasan yang sangat kuat untuk keluar dari model ini.

Ketika Anda berbicara tentang ingin mencegat panggilan fungsi, bagi saya kedengarannya seperti Anda mencoba menciptakan kembali fungsi virtual. Ada banyak sekali cara untuk mengeluarkan fungsionalitas semacam ini, seperti mewarisi dari kelas dasar dan menimpa fungsinya.

Kedengarannya bagi saya seperti Anda menginginkan kelas yang lebih merupakan pembungkus daripada turunan turunan dari kelas dasar, jadi lakukan sesuatu seperti ini:

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) ATAU petakan input fungsi Anda ke antarmuka umum.

Ini mungkin berfungsi jika semua fungsi Anda terkait. Misalnya, jika Anda sedang menulis game, dan semua fungsi melakukan sesuatu pada beberapa bagian dari pemain atau inventaris pemain. Anda akan berakhir dengan sesuatu seperti ini:

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);
    }
}
Juliet
sumber
Asumsi saya akan menjadi objectseperti itu yang akan diteruskan ke Thread baru.
Scott Fraley
1

Hei, saya harap ini membantu. Kamu berasal dari bahasa apa

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);
    }
}
orang pintar
sumber
1

Mengapa tidak menggunakan params object[] listparameter metode dan melakukan beberapa validasi di dalam metode Anda (atau logika panggilan), Ini akan memungkinkan sejumlah variabel parameter.

wvd_vegt
sumber
1

Skenario berikut akan memungkinkan Anda menggunakan kamus elemen untuk dikirim sebagai parameter masukan dan mendapatkan parameter yang sama sebagai parameter keluaran.

Pertama tambahkan baris berikut di atas:

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

Kemudian di dalam kelas Anda, tentukan kamus sebagai berikut:

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

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

Ini akan memungkinkan Anda untuk menjalankan fungsi anonim ini dengan sintaks yang mirip dengan ini:

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

Definisikan kamus dan tambahkan referensi fungsi sebagai nilainya, menggunakan System.Actionsebagai tipe:

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

Kemudian panggil dengan:

Actions.myActions["myKey"]();
modle13
sumber