C # Lưu trữ các hàm trong Từ điển


93

Làm cách nào để tạo Từ điển nơi tôi có thể lưu trữ các hàm?

Cảm ơn.

Tôi có khoảng hơn 30 chức năng có thể được thực thi từ người dùng. Tôi muốn có thể thực thi hàm theo cách này:

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

Tuy nhiên, chữ ký hàm không phải lúc nào cũng giống nhau, do đó có số lượng đối số khác nhau.


5
Đây là một thành ngữ tuyệt vời - có thể thay thế các câu lệnh chuyển đổi khó chịu.
Hamish Grubijan

Hàm ví dụ của bạn có các tham số, tuy nhiên, khi bạn gọi hàm được lưu trữ, bạn đang gọi nó mà không có bất kỳ đối số nào. Các đối số có được sửa khi hàm được lưu trữ không?
Tim Lloyd

1
@HamishGrubijan, nếu bạn làm điều này để thay thế một câu lệnh switch thì bạn đang loại bỏ tất cả sự rõ ràng và tối ưu hóa thời gian biên dịch. Vì vậy, tôi muốn nói nó ngốc hơn là thành ngữ. Nếu bạn muốn tự động ánh xạ chức năng theo cách có thể thay đổi trong thời gian chạy, thì nó có thể hữu ích.
Jodrell

12
@Jodrell, A) Ngu ngốc là một từ mạnh mẽ không mang tính xây dựng, B) Sự rõ ràng là trong mắt của một người xử sự. Tôi đã thấy rất nhiều tuyên bố chuyển đổi xấu xí. C) Tối ưu hóa thời gian biên dịch ... Zooba trên chủ đề này lập luận cho điều ngược lại stackoverflow.com/questions/505454/… Nếu câu lệnh switch là O (log N), thì nó sẽ phải chứa hơn 100 trường hợp để tốc độ tạo ra sự khác biệt . Điều đó không thể đọc được, tất nhiên. Có thể một câu lệnh switch có thể sử dụng một hàm băm hoàn hảo, nhưng chỉ đối với một số ít trường hợp. Nếu bạn đang sử dụng .Net, thì bạn không cần lo lắng về micro giây.
Hamish Grubijan

4
@Jodrell, (tiếp theo) Nếu bạn muốn khai thác tối đa phần cứng của mình, thì bạn sử dụng ASM, C hoặc C ++ theo thứ tự đó. Tra cứu từ điển trong .Net sẽ không giết bạn. Các chữ ký khác nhau của các đại biểu - đó là điểm mạnh nhất mà bạn tạo ra khi sử dụng cách tiếp cận từ điển đơn giản.
Hamish Grubijan

Câu trả lời:


120

Như thế này:

Dictionary<int, Func<string, bool>>

Điều này cho phép bạn lưu trữ các hàm nhận tham số chuỗi và trả về boolean.

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

Hoặc nếu hàm không ẩn danh:

dico[5] = Foo;

nơi Foo được định nghĩa như thế này:

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

CẬP NHẬT:

Sau khi xem bản cập nhật của bạn, có vẻ như bạn không biết trước chữ ký của hàm mà bạn muốn gọi. Trong .NET để gọi một hàm, bạn cần phải chuyển tất cả các đối số và nếu bạn không biết các đối số là gì thì cách duy nhất để đạt được điều này là thông qua phản chiếu.

Và đây là một giải pháp thay thế khác:

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

Với cách tiếp cận này, bạn vẫn cần biết số lượng và loại tham số cần được truyền cho mỗi hàm tại chỉ mục tương ứng của từ điển, nếu không bạn sẽ gặp lỗi thời gian chạy. Và nếu các hàm của bạn không có giá trị trả về, hãy sử dụng System.Action<>thay thế System.Func<>.


Tôi hiểu rồi. Tôi đoán tôi sẽ phải dành một chút thời gian để đọc về sự suy ngẫm sau đó, đánh giá cao sự giúp đỡ.
chi

@chi, xem bản cập nhật cuối cùng của tôi. Tôi đã thêm một ví dụ với Dictionary<int, Delegate>.
Darin Dimitrov

3
Tôi sẽ không downvote, nhưng tôi sẽ chỉ có thể nói loại này thực hiện là một antipattern không rất lý do chính đáng. Nếu OP mong đợi các máy khách chuyển tất cả các đối số vào hàm, thì tại sao lại có một bảng hàm ở vị trí đầu tiên? Tại sao khách hàng không chỉ cần gọi các chức năng mà không có sự kỳ diệu của các lệnh gọi động?
Juliet,

1
@Juliet, tôi đồng ý với bạn, tôi chưa bao giờ nói đó là điều tốt. Nhân tiện trong câu trả lời của tôi, tôi nhấn mạnh vào thực tế là chúng ta vẫn cần biết số lượng và loại tham số cần được truyền cho mỗi hàm tại chỉ mục tương ứng của từ điển. Bạn hoàn toàn chính xác khi chỉ ra rằng trong trường hợp này, chúng ta có thể không cần đến bảng băm vì chúng ta có thể gọi trực tiếp hàm.
Darin Dimitrov

1
Điều gì sẽ xảy ra nếu tôi cần lưu trữ một hàm không nhận bất kỳ đối số nào và không có giá trị trả về?
DataGreed

8

Tuy nhiên, chữ ký hàm không phải lúc nào cũng giống nhau, do đó có số lượng đối số khác nhau.

Hãy bắt đầu với một vài hàm được định nghĩa như sau:

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

Bạn thực sự có 2 lựa chọn khả thi theo ý của mình:

1) Duy trì an toàn kiểu bằng cách để khách hàng gọi trực tiếp cho chức năng của bạn.

Đây có lẽ là giải pháp tốt nhất, trừ khi bạn có rất lý do chính đáng để phá vỡ từ mô hình này.

Khi bạn nói về việc muốn chặn các lệnh gọi hàm, tôi nghe có vẻ như bạn đang cố gắng phát minh lại các hàm ảo. Có rất nhiều cách để lấy loại chức năng này ra khỏi hộp, chẳng hạn như kế thừa từ một lớp cơ sở và ghi đè các chức năng của nó.

Đối với tôi, có vẻ như bạn muốn một lớp giống như một trình bao bọc hơn là một thể hiện dẫn xuất của một lớp cơ sở, vì vậy hãy làm như sau:

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) HOẶC ánh xạ đầu vào của các chức năng của bạn với một giao diện chung.

Điều này có thể hoạt động nếu tất cả các chức năng của bạn có liên quan. Ví dụ: nếu bạn đang viết một trò chơi và tất cả các chức năng đều tác động đến một số phần của trình phát hoặc kho của người chơi. Bạn sẽ kết thúc với một cái gì đó như thế này:

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

Giả định của tôi sẽ là một objectnhư thế sẽ được chuyển đến một Chủ đề mới.
Scott Fraley

1

Này, tôi hy vọng điều này sẽ hữu ích. Bạn đến từ ngôn ngữ nào?

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

Tại sao không sử dụng params object[] listcho các tham số phương thức và thực hiện một số xác nhận bên trong các phương thức của bạn (hoặc logic gọi), Nó sẽ cho phép một số lượng tham số thay đổi.


1

Tình huống sau đây sẽ cho phép bạn sử dụng từ điển các phần tử để gửi vào dưới dạng tham số đầu vào và nhận giống như tham số đầu ra.

Đầu tiên hãy thêm dòng sau ở trên cùng:

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

Sau đó, bên trong lớp của bạn, xác định từ điển như sau:

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

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

Điều này sẽ cho phép bạn chạy các hàm ẩn danh này với cú pháp tương tự như sau:

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

0

Xác định từ điển và thêm tham chiếu hàm làm giá trị, sử dụng System.Actionlàm kiểu:

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

Sau đó, gọi nó bằng:

Actions.myActions["myKey"]();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.