ฟังก์ชั่น C # Store ในพจนานุกรม


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

อย่างไรก็ตามลายเซ็นของฟังก์ชันจะไม่เหมือนกันเสมอไปดังนั้นจึงมีจำนวนอาร์กิวเมนต์ที่แตกต่างกัน


7
นี่เป็นสำนวนที่ยอดเยี่ยม - สามารถแทนที่ข้อความสวิตช์ที่น่ารังเกียจ
Hamish Grubijan

1
นั่นเป็นเพียงตัวอย่างที่เขียนไม่ดีด้วยตัวเอง ฉันไม่รู้ว่าจะสร้างพจนานุกรมที่จะทำได้เพราะลายเซ็นของฟังก์ชันนั้นแตกต่างกันเสมอ
ไค

1
@HamishGrubijan หากคุณทำสิ่งนี้เพื่อแทนที่คำสั่งสวิตช์แสดงว่าคุณกำลังละทิ้งการเพิ่มประสิทธิภาพเวลาคอมไพล์และความชัดเจนทั้งหมด ฉันจะบอกว่ามันงี่เง่ามากกว่าสำนวน หากคุณต้องการแมปฟังก์ชันการทำงานแบบไดนามิกในรูปแบบที่อาจแตกต่างกันไปในขณะรันไทม์นั่นอาจเป็นประโยชน์
Jodrell

14
@ Jodrell, A) Idiot เป็นคำที่หนักแน่นและไม่สร้างสรรค์ B) ความชัดเจนอยู่ในสายตาของผู้มอง ฉันได้เห็นข้อความสวิตช์ที่น่าเกลียดมากมาย C) การเพิ่มประสิทธิภาพเวลาคอมไพล์ ... Zooba ในเธรดนี้โต้แย้งสำหรับstackoverflow.com/questions/505454/ ที่ตรงกันข้ามหากคำสั่ง switch เป็น O (log N) ก็จะต้องมี 100+ กรณีเพื่อความเร็วในการสร้างความแตกต่าง . ที่อ่านไม่ออกแน่นอน บางทีคำสั่ง switch สามารถใช้ฟังก์ชันแฮชที่สมบูรณ์แบบได้ แต่สำหรับกรณีจำนวนน้อยเท่านั้น หากคุณใช้. Net คุณจะไม่หงุดหงิดในช่วงไมโครวินาที
Hamish Grubijan

4
@Jodrell, (ต่อ) หากคุณต้องการบีบฮาร์ดแวร์ของคุณให้เกิดประโยชน์สูงสุดให้ใช้ ASM, C หรือ C ++ ตามลำดับนั้น การค้นหาพจนานุกรมใน. Net จะไม่ฆ่าคุณ ลายเซ็นต่างๆของผู้ได้รับมอบหมาย - นั่นคือจุดแข็งที่สุดที่คุณต่อต้านโดยใช้วิธีพจนานุกรมง่ายๆ
Hamish Grubijan

คำตอบ:


121

แบบนี้:

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 คาดหวังให้ไคลเอ็นต์ส่งผ่านอาร์กิวเมนต์ทั้งหมดไปยังฟังก์ชันเหตุใดจึงต้องมีตารางฟังก์ชันตั้งแต่แรก ทำไมลูกค้าไม่เพียงแค่เรียกใช้ฟังก์ชันโดยไม่ต้องใช้ความมหัศจรรย์ของการเรียกใช้แบบไดนามิก?
Juliet

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

2

กำหนดพจนานุกรมและเพิ่มการอ้างอิงฟังก์ชันเป็นค่าโดยใช้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"]();

คำตอบปกติใน 99% ของกรณี
Fattie

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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.