วิธีการส่งผ่านเป็นพารามิเตอร์โดยใช้ C #


695

ฉันมีหลายวิธีทั้งหมดที่มีลายเซ็นเดียวกัน (พารามิเตอร์และค่าส่งคืน) แต่ชื่อแตกต่างกันและ internals ของวิธีการจะแตกต่างกัน ฉันต้องการส่งชื่อวิธีการเพื่อเรียกใช้ไปยังวิธีอื่นที่จะเรียกใช้วิธีการส่งผ่าน

public int Method1(string)
{
    ... do something
    return myInt;
}

public int Method2(string)
{
    ... do something different
    return myInt;
}

public bool RunTheMethod([Method Name passed in here] myMethodName)
{
    ... do stuff
    int i = myMethodName("My String");
    ... do more stuff
    return true;
}

public bool Test()
{
    return RunTheMethod(Method1);
}

รหัสนี้ใช้ไม่ได้ แต่นี่คือสิ่งที่ฉันพยายามจะทำ สิ่งที่ฉันไม่เข้าใจคือวิธีการเขียนรหัส RunTheMethod เนื่องจากฉันต้องการกำหนดพารามิเตอร์


12
ทำไมคุณไม่ส่งผู้แทนแทนชื่อของวิธีการ?
Mark Byers

คำตอบ:


854

คุณสามารถใช้ผู้รับมอบสิทธิ์ Func ใน. net 3.5 เป็นพารามิเตอร์ในวิธี RunTheMethod ของคุณ ผู้รับมอบสิทธิ์ Func ช่วยให้คุณสามารถระบุวิธีการที่ใช้พารามิเตอร์จำนวนของประเภทที่เฉพาะเจาะจงและส่งกลับอาร์กิวเมนต์เดียวของประเภทที่เฉพาะเจาะจง นี่คือตัวอย่างที่ควรใช้:

public class Class1
{
    public int Method1(string input)
    {
        //... do something
        return 0;
    }

    public int Method2(string input)
    {
        //... do something different
        return 1;
    }

    public bool RunTheMethod(Func<string, int> myMethodName)
    {
        //... do stuff
        int i = myMethodName("My String");
        //... do more stuff
        return true;
    }

    public bool Test()
    {
        return RunTheMethod(Method1);
    }
}

51
Func call จะเปลี่ยนไปอย่างไรถ้าเมธอดนั้นเป็นลายเซ็นต์ของการคืนค่าเป็นโมฆะและไม่มีพารามิเตอร์? ฉันดูเหมือนจะไม่สามารถใช้งานไวยากรณ์ได้
user31673

211
@unknown: ในกรณีที่ว่ามันจะเป็นแทนAction Func<string, int>
Jon Skeet

12
แต่ตอนนี้ถ้าคุณต้องการส่งผ่านข้อโต้แย้งไปยังวิธีการ?
john ktejik

40
@ user396483 ตัวอย่างเช่นAction<int,string>สอดคล้องกับวิธีการซึ่งรับพารามิเตอร์ 2 ตัว (int และ string) และการคืนค่าเป็นโมฆะ
serdar

24
@NoelWidmer ใช้Func<double,string,int>สอดคล้องกับวิธีการที่จะใช้เวลา 2 พารามิเตอร์ (กdoubleและstring) intและกลับมา ประเภทที่ระบุล่าสุดคือประเภทส่งคืน คุณสามารถใช้ผู้รับมอบสิทธิ์นี้ได้สูงสุด 16 พารามิเตอร์ public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);หากคุณต้องการมากขึ้นอย่างใดเขียนผู้รับมอบสิทธิ์ของคุณเองเป็น โปรดแก้ไขฉันหากฉันเข้าใจผิด
serdar

356

คุณจำเป็นต้องใช้ผู้ร่วมประชุม ในกรณีนี้วิธีการของคุณทุกคนจะstringพารามิเตอร์และกลับint- นี้เป็นตัวแทนส่วนใหญ่ก็โดยFunc<string, int>ผู้ร่วมประชุม1 ดังนั้นรหัสของคุณสามารถแก้ไขได้อย่างง่ายดายโดยการเปลี่ยนแปลงดังนี้

public bool RunTheMethod(Func<string, int> myMethodName)
{
    // ... do stuff
    int i = myMethodName("My String");
    // ... do more stuff
    return true;
}

ผู้เข้าร่วมมีอำนาจมากกว่านี้เป็นที่ยอมรับ ตัวอย่างเช่นด้วย C # คุณสามารถสร้างผู้รับมอบสิทธิ์จากการแสดงออกแลมบ์ดาดังนั้นคุณสามารถเรียกใช้วิธีการของคุณด้วยวิธีนี้:

RunTheMethod(x => x.Length);

ที่จะสร้างฟังก์ชั่นนิรนามดังนี้:

// The <> in the name make it "unspeakable" - you can't refer to this method directly
// in your own code.
private static int <>_HiddenMethod_<>(string x)
{
    return x.Length;
}

จากนั้นส่งผู้แทนไปยังRunTheMethodวิธีการ

คุณสามารถใช้ผู้ได้รับมอบหมายสำหรับการสมัครสมาชิกกิจกรรมการดำเนินการแบบอะซิงโครนัสการโทรกลับ - ทุกสิ่ง มันคุ้มค่าที่จะอ่านมันโดยเฉพาะถ้าคุณต้องการใช้ LINQ ฉันมีบทความซึ่งส่วนใหญ่เกี่ยวกับความแตกต่างระหว่างผู้ได้รับมอบหมายและเหตุการณ์ แต่คุณอาจพบว่ามันมีประโยชน์อยู่ดี


1สิ่งนี้ขึ้นอยู่กับFunc<T, TResult>ประเภทตัวแทนทั่วไปในกรอบงาน คุณสามารถประกาศของคุณเองได้อย่างง่ายดาย:

public delegate int MyDelegateType(string value)

จากนั้นทำให้พารามิเตอร์เป็นประเภทMyDelegateTypeแทน


59
+1 นี่เป็นคำตอบที่น่าอัศจรรย์จริงๆในการสั่นสะเทือนภายในสองนาที
David Hall

3
ในขณะที่คุณสามารถส่งผ่านฟังก์ชันโดยใช้ผู้รับมอบสิทธิ์แนวทาง OO แบบดั้งเดิมมากขึ้นจะใช้รูปแบบกลยุทธ์
เปาโล

21
@ เปาโล: ผู้ได้รับมอบหมายเป็นเพียงการนำรูปแบบกลยุทธ์มาใช้อย่างสะดวกสบายซึ่งกลยุทธ์ดังกล่าวต้องการเพียงวิธีเดียว มันไม่เหมือนกับสิ่งนี้ขัดกับรูปแบบกลยุทธ์ - แต่มันสะดวกกว่าการใช้รูปแบบโดยใช้อินเตอร์เฟส
Jon Skeet

5
ผู้แทน "คลาสสิค" (ที่รู้จักกันในชื่อ. NET 1/2) ยังคงมีประโยชน์หรือไม่หรือล้าสมัยเนื่องจาก Func / Action หรือไม่ นอกจากนี้ยังไม่ได้มีคำหลักที่ผู้ร่วมประชุมที่ขาดหายไปในตัวอย่างของคุณpublic **delegate** int MyDelegateType(string value)?
M4N

1
@ มาร์ติน: Doh! ขอบคุณสำหรับการแก้ไข แก้ไข สำหรับการประกาศชื่อผู้รับมอบสิทธิ์ของคุณ - มันมีประโยชน์ที่จะให้ชื่อประเภทความหมายบางอย่าง แต่ฉันไม่ค่อยได้สร้างประเภทตัวแทนของตัวเองตั้งแต่. NET 3.5
Jon Skeet

113

จากตัวอย่างของ OP:

 public static int Method1(string mystring)
 {
      return 1;
 }

 public static int Method2(string mystring)
 {
     return 2;
 }

คุณสามารถลองผู้แทนจาก Action! แล้วเรียกวิธีการของคุณโดยใช้

 public bool RunTheMethod(Action myMethodName)
 {
      myMethodName();   // note: the return value got discarded
      return true;
 }

RunTheMethod(() => Method1("MyString1"));

หรือ

public static object InvokeMethod(Delegate method, params object[] args)
{
     return method.DynamicInvoke(args);
}

จากนั้นเรียกวิธีการ

Console.WriteLine(InvokeMethod(new Func<string,int>(Method1), "MyString1"));

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));

4
ขอบคุณสิ่งนี้ทำให้ฉันได้รับสิ่งที่ฉันต้องการเพราะฉันต้องการวิธี "RunTheMethod" ทั่วไปที่จะอนุญาตให้มีพารามิเตอร์หลายตัว Btw การInvokeMethodโทรแลมบ์ดาครั้งแรกของคุณควรเป็นRunTheMethodแทน
จอห์

1
เช่นเดียวกับจอห์นสิ่งนี้ช่วยฉันได้จริง ๆ กับวิธีการทั่วไป ขอบคุณ!
ean5533

2
คุณทำวันของฉัน;) ใช้งานง่ายและยืดหยุ่นกว่าคำตอบที่เลือก IMO
Sidewinder94

มีวิธีการขยายใน RunTheMethod (() => Method1 ("MyString1")); การเรียกคืนค่า นึกคิดทั่วไปหรือไม่
Jay

ถ้าคุณต้องการส่งผ่านพารามิเตอร์ให้ระวังสิ่งนี้: stackoverflow.com/a/5414539/2736039
Ultimo_m

31
public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

การใช้งาน:

var ReturnValue = Runner(() => GetUser(99));

6
มันใช้งานง่ายมาก ด้วยวิธีนี้สามารถใช้หนึ่งหรือหลายพารามิเตอร์ ฉันเดาคำตอบล่าสุดคือ
bafsar

ฉันต้องการเพิ่มสิ่งหนึ่งเกี่ยวกับการใช้งานนี้ หากวิธีที่คุณจะผ่านมีประเภทเป็นโมฆะคุณจะไม่สามารถใช้วิธีนี้ได้
Imants Volkovs

@ImantsVolkovs ฉันเชื่อว่าคุณอาจสามารถแก้ไขสิ่งนี้เพื่อใช้ Action แทนที่จะเป็น Func และเปลี่ยนลายเซ็นเป็นโมฆะ ไม่แน่ใจ 100%
Shockwave

2
มีวิธีการรับพารามิเตอร์ที่ส่งผ่านไปยังฟังก์ชันที่เรียกว่า?
จิมมี่

17

สำหรับการแบ่งปันโซลูชันที่สมบูรณ์ที่สุดเท่าที่เป็นไปได้ฉันจะจบลงด้วยการนำเสนอสามวิธีที่แตกต่างกัน แต่ตอนนี้ฉันจะเริ่มจากหลักการพื้นฐานที่สุด


แนะนำสั้น ๆ

ภาษาทั้งหมดที่ทำงานบน CLR ( Common Language Runtime ) เช่น C #, F # และ Visual Basic ทำงานภายใต้ VM ซึ่งรันโค้ดในระดับที่สูงกว่าภาษาดั้งเดิมเช่น C และ C ++ (ซึ่งรวบรวมโดยตรงกับเครื่องจักร รหัส). มันเป็นไปตามวิธีการที่ว่านั้นไม่ใช่บล็อกที่ถูกคอมไพล์ แต่เป็นเพียงองค์ประกอบที่มีโครงสร้างที่ CLR รู้จัก ดังนั้นคุณไม่สามารถคิดว่าจะส่งผ่านเมธอดเป็นพารามิเตอร์ได้เนื่องจากเมธอดไม่สร้างค่าใด ๆ ด้วยตัวเองเนื่องจากไม่ใช่การแสดงออก! แต่พวกเขากำลังงบซึ่งกำหนดไว้ในรหัส CIL ที่สร้างขึ้น ดังนั้นคุณจะต้องเผชิญกับแนวคิดผู้แทน


ตัวแทนคืออะไร?

ผู้รับมอบสิทธิ์แทนตัวชี้ไปยังวิธีการ ดังที่ฉันได้กล่าวไว้ข้างต้นวิธีการไม่ได้เป็นค่าดังนั้นจึงมีคลาสพิเศษในภาษา CLR Delegateซึ่งสรุปวิธีการใด ๆ

ดูตัวอย่างต่อไปนี้:

static void MyMethod()
{
    Console.WriteLine("I was called by the Delegate special class!");
}

static void CallAnyMethod(Delegate yourMethod)
{
    yourMethod.DynamicInvoke(new object[] { /*Array of arguments to pass*/ });
}

static void Main()
{
    CallAnyMethod(MyMethod);
}

สามวิธีที่แตกต่างแนวคิดเดียวกันที่อยู่เบื้องหลัง:

  • วิธีที่ 1
    ใช้Delegateคลาสพิเศษโดยตรงดังตัวอย่างข้างต้น ปัญหาของการแก้ปัญหานี้คือรหัสของคุณจะไม่ถูกตรวจสอบในขณะที่คุณผ่านข้อโต้แย้งของคุณแบบไดนามิกโดยไม่ จำกัด ให้เป็นประเภทของเหล่านั้นในการกำหนดวิธีการ

  • วิธีที่ 2
    นอกเหนือจากDelegateคลาสพิเศษแนวคิดของผู้มอบหมายจะกระจายไปยังผู้ร่วมประชุมที่กำหนดเองซึ่งเป็นคำจำกัดความของวิธีที่นำหน้าด้วยdelegateคำหลักและพวกเขาปฏิบัติเช่นเดียวกับวิธีการปกติ พวกเขาจะถูกตรวจสอบดังนั้นคุณจะพบกับรหัสที่ปลอดภัยไร้ที่ติ

    นี่คือตัวอย่าง:

    delegate void PrintDelegate(string prompt);
    
    static void PrintSomewhere(PrintDelegate print, string prompt)
    {
        print(prompt);
    }
    
    static void PrintOnConsole(string prompt)
    {
        Console.WriteLine(prompt);
    }
    
    static void PrintOnScreen(string prompt)
    {
        MessageBox.Show(prompt);
    }
    
    static void Main()
    {
        PrintSomewhere(PrintOnConsole, "Press a key to get a message");
        Console.Read();
        PrintSomewhere(PrintOnScreen, "Hello world");
    }
    
  • วิธีที่ 3
    หรืออีกวิธีหนึ่งคุณสามารถใช้ผู้รับมอบสิทธิ์ที่ได้กำหนดไว้แล้วใน. NET Standard:

    • Actionตัดคำvoidโดยไม่มีข้อโต้แย้ง
    • Action<T1>ล้อมรอบvoidด้วยอาร์กิวเมนต์หนึ่งตัว
    • Action<T1, T2>ล้อมรอบvoidด้วยa สองข้อโต้แย้ง
    • และอื่น ๆ ...
    • Func<TR>ล้อมฟังก์ชันที่มีTRชนิดส่งคืนและไม่มีอาร์กิวเมนต์
    • Func<T1, TR>ล้อมฟังก์ชันที่มีTRประเภทส่งคืนและมีอาร์กิวเมนต์หนึ่งรายการ
    • Func<T1, T2, TR>ล้อมฟังก์ชันที่มีTRชนิดส่งคืนและมีสองอาร์กิวเมนต์
    • และอื่น ๆ ...

(โซลูชันหลังคือโพสต์ที่คนส่วนใหญ่โพสต์)


1
ประเภทการคืนของ Func <T> ไม่ควรเป็นประเภทสุดท้ายใช่หรือไม่ Func<T1,T2,TR>
sanmcp

13

คุณควรใช้Func<string, int>ผู้รับมอบสิทธิ์ซึ่งแสดงถึงฟังก์ชั่นที่รับstringอาร์กิวเมนต์เป็นและส่งคืนint:

public bool RunTheMethod(Func<string, int> myMethod) {
    // do stuff
    myMethod.Invoke("My String");
    // do stuff
    return true;
}

จากนั้นใช้:

public bool Test() {
    return RunTheMethod(Method1);
}

3
สิ่งนี้จะไม่รวบรวม Testวิธีการที่ควรจะเป็นreturn RunTheMethod(Method1);
pswg

7

หากคุณต้องการความสามารถในการเปลี่ยนวิธีการที่เรียกว่า ณ รันไทม์ฉันขอแนะนำให้ใช้ผู้รับมอบสิทธิ์: http://www.codeproject.com/KB/cs/delegates_step1.aspx

มันจะช่วยให้คุณสร้างวัตถุเพื่อจัดเก็บวิธีการโทรและคุณสามารถส่งสิ่งนั้นไปยังวิธีอื่นของคุณเมื่อจำเป็น


2

ในขณะที่คำตอบที่ยอมรับนั้นถูกต้องอย่างแน่นอนฉันต้องการให้วิธีการเพิ่มเติม

ฉันลงเอยที่นี่หลังจากค้นหาคำตอบสำหรับคำถามที่คล้ายกัน ฉันกำลังสร้างเฟรมเวิร์กที่ขับเคลื่อนด้วยปลั๊กอินและเป็นส่วนหนึ่งของมันฉันต้องการให้ผู้คนสามารถเพิ่มรายการเมนูลงในเมนูแอปพลิเคชันไปยังรายการทั่วไปโดยไม่ต้องเปิดเผยMenuวัตถุจริงเพราะกรอบอาจใช้งานบนแพลตฟอร์มอื่นที่ไม่มีMenuUI วัตถุ การเพิ่มข้อมูลทั่วไปเกี่ยวกับเมนูนั้นง่ายพอ แต่การอนุญาตให้นักพัฒนาปลั๊กอินมีอิสระมากพอที่จะสร้างการโทรกลับเมื่อเมนูถูกคลิกก็พิสูจน์ได้ว่าเป็นเรื่องที่เจ็บปวด จนกระทั่งถึงรุ่งสางกับฉันว่าฉันกำลังพยายามคิดค้นวงล้อและเมนูปกติโทรและเรียกการโทรกลับจากเหตุการณ์!

ดังนั้นวิธีการแก้ปัญหาที่เรียบง่ายอย่างที่มันฟังเมื่อคุณตระหนักถึงมันได้ทำให้ฉันหลงทางจนถึงตอนนี้

เพียงสร้างคลาสที่แยกต่างหากสำหรับแต่ละวิธีปัจจุบันของคุณสืบทอดมาจากฐานถ้าคุณต้องการและเพียงแค่เพิ่มตัวจัดการเหตุการณ์ให้กับแต่ละคน


1

นี่คือตัวอย่างซึ่งสามารถช่วยให้คุณเข้าใจวิธีการส่งผ่านฟังก์ชั่นเป็นพารามิเตอร์ได้ดียิ่งขึ้น

สมมติว่าคุณมีผู้ปกครองหน้าและคุณต้องการที่จะเปิดหน้าต่างลูกป๊อปอัพ ในหน้าหลักมีกล่องข้อความที่ควรกรอกตามกล่องข้อความป๊อปอัพเด็ก

ที่นี่คุณต้องสร้างผู้รับมอบสิทธิ์

Parent.cs // การประกาศของผู้รับมอบสิทธิ์สาธารณะโมฆะ FillName (String FirstName);

ตอนนี้สร้างฟังก์ชั่นที่จะเติมกล่องข้อความของคุณและฟังก์ชั่นควรแมปผู้ได้รับมอบหมาย

//parameters
public void Getname(String ThisName)
{
     txtname.Text=ThisName;
}

ตอนนี้คลิกปุ่มคุณต้องเปิดหน้าต่างป๊อปอัพเด็ก

  private void button1_Click(object sender, RoutedEventArgs e)
  {
        ChildPopUp p = new ChildPopUp (Getname) //pass function name in its constructor

         p.Show();

    }

ในตัวสร้าง ChildPopUp คุณต้องสร้างพารามิเตอร์ของ 'ประเภทผู้รับมอบสิทธิ์' ของ parent // หน้า

ChildPopUp.cs

    public  Parent.FillName obj;
    public PopUp(Parent.FillName objTMP)//parameter as deligate type
    {
        obj = objTMP;
        InitializeComponent();
    }



   private void OKButton_Click(object sender, RoutedEventArgs e)
    {


        obj(txtFirstName.Text); 
        // Getname() function will call automatically here
        this.DialogResult = true;
    }

แก้ไขแล้ว แต่ยังคงสามารถปรับปรุงคุณภาพของคำตอบนี้ได้
SteakOverflow

1

หากคุณต้องการส่งเมธอดเป็นพารามิเตอร์ให้ใช้:

using System;

public void Method1()
{
    CallingMethod(CalledMethod);
}

public void CallingMethod(Action method)
{
    method();   // This will call the method that has been passed as parameter
}

public void CalledMethod()
{
    Console.WriteLine("This method is called by passing parameter");
}

0

นี่คือตัวอย่างที่ไม่มีพารามิเตอร์: http://en.csharp-online.net/CSharp_FAQ:_How_call_a_method_using_a_name_string

กับ params: http://www.daniweb.com/forums/thread98148.html#

โดยทั่วไปคุณจะผ่านไปในชุดของวัตถุพร้อมกับชื่อของวิธีการ จากนั้นใช้ทั้งสองวิธีด้วยวิธีการเรียกใช้

พารามิเตอร์วัตถุ []


โปรดทราบว่าชื่อของวิธีการไม่ได้อยู่ในสตริง - จริง ๆ แล้วมันเป็นกลุ่มวิธี ผู้ได้รับมอบหมายเป็นคำตอบที่ดีที่สุดที่นี่ไม่ใช่ภาพสะท้อน
Jon Skeet

@Lette: ในวิธีการอุทธรณ์การแสดงออกที่ใช้เป็นอาร์กิวเมนต์เป็นกลุ่มวิธีการ ; มันเป็นชื่อของวิธีการซึ่งเป็นที่รู้จักกันในเวลารวบรวมและคอมไพเลอร์สามารถแปลงเป็นตัวแทน สิ่งนี้แตกต่างจากสถานการณ์ที่ทราบชื่อในเวลาดำเนินการเท่านั้น
Jon Skeet

0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

คลาสที่สองคือไคลเอนต์ซึ่งจะใช้คลาสหน่วยเก็บข้อมูล มันมีวิธีการหลักที่สร้างอินสแตนซ์ของ PersonDB และจะเรียกวิธีการของวัตถุนั้นด้วยวิธีการที่กำหนดไว้ในระดับลูกค้า

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.