วิธีสร้างฟังก์ชันอินไลน์ใน C #


98

ฉันใช้ Linq To XML

new XElement("Prefix", Prefix == null ? "" : Prefix)

แต่ฉันต้องการคำนวณค่านำหน้าก่อนที่จะเพิ่มลงใน xml เช่นการกำจัดช่องว่างตัวอักษรพิเศษการคำนวณบางอย่างเป็นต้น

ฉันไม่ต้องการสร้างฟังก์ชันเพราะฟังก์ชันนี้จะไม่ช่วยในส่วนอื่น ๆ ของโปรแกรมของฉัน แต่สิ่งนี้มีวิธีใดบ้างในการสร้างฟังก์ชันอินไลน์ ??


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

14
หากคุณมาที่นี่จาก Google และต้องการทราบวิธีจำลองฟังก์ชันแบบอินไลน์ใน c # โปรดไปที่นี่: stackoverflow.com/questions/473782/inline-functions-in-c คำตอบด้านล่างไม่เกี่ยวข้องกับฟังก์ชัน "อินไลน์" ในความหมายที่แท้จริงของคำและ c # ไม่มีฟังก์ชันอินไลน์ที่แท้จริง แต่ลิงก์ด้านบนมีการเพิ่มประสิทธิภาพคอมไพเลอร์ที่สามารถใช้ได้
JamesHoux

ขอบคุณเจมส์ชื่อคำถามค่อนข้างทำให้เข้าใจผิด
Zar Shardan

คำตอบ:


227

ใช่ C # รองรับสิ่งนั้น มีไวยากรณ์หลายแบบ

  • เพิ่มวิธีการไม่ระบุชื่อใน C # 2.0:

    Func<int, int, int> add = delegate(int x, int y)
    {
        return x + y;
    };
    Action<int> print = delegate(int x)
    {
        Console.WriteLine(x);
    }
    Action<int> helloWorld = delegate // parameters can be elided if ignored
    {
        Console.WriteLine("Hello world!");
    }
    
  • Lambdasเป็นผลิตภัณฑ์ใหม่ใน C # 3.0 และมีสองรสชาติ

    • lambdas นิพจน์:

      Func<int, int, int> add = (int x, int y) => x + y; // or...
      Func<int, int, int> add = (x, y) => x + y; // types are inferred by the compiler
      
    • คำชี้แจง lambdas:

      Action<int> print = (int x) => { Console.WriteLine(x); };
      Action<int> print = x => { Console.WriteLine(x); }; // inferred types
      Func<int, int, int> add = (x, y) => { return x + y; };
      
  • มีการนำฟังก์ชั่นท้องถิ่นมาใช้กับ C # 7.0:

    int add(int x, int y) => x + y;
    void print(int x) { Console.WriteLine(x); }
    

โดยทั่วไปมีสองประเภทที่แตกต่างกันสำหรับสิ่งเหล่านี้: FuncและAction. Funcs ส่งคืนค่า แต่Actionไม่ทำ พารามิเตอร์ประเภทสุดท้ายของ a Funcคือประเภทการส่งคืน อื่น ๆ ทั้งหมดเป็นประเภทพารามิเตอร์

มีประเภทที่คล้ายกันโดยมีชื่อต่างกัน แต่ไวยากรณ์สำหรับการประกาศแบบอินไลน์จะเหมือนกัน ตัวอย่างคือComparison<T>ซึ่งเทียบเท่ากับFunc<T, T, int>.

Func<string, string, int> compare1 = (l,r) => 1;
Comparison<string> compare2 = (l, r) => 1;
Comparison<string> compare3 = compare1; // this one only works from C# 4.0 onwards

สิ่งเหล่านี้สามารถเรียกใช้ได้โดยตรงราวกับว่าเป็นวิธีการปกติ:

int x = add(23, 17); // x == 40
print(x); // outputs 40
helloWorld(x); // helloWorld has one int parameter declared: Action<int>
               // even though it does not make any use of it.

4
ฉันไม่เข้าใจคำตอบนี้ ในตัวอย่างของเขาเขาต้องการ XElement ใหม่ ("คำนำหน้า", คำนำหน้า => newPrefix) ที่ประกาศในบรรทัด คำตอบทั้งหมดนี้มีค่าเท่ากับการประกาศฟังก์ชันใหม่ ทำไมไม่เพียงแค่ประกาศฟังก์ชันใหม่?
Matthew James Davis

3
เนื่องจาก: 1. การตอบสนองลอจิกของวิธีการอ่านฟังก์ชันอินไลน์นั้นง่ายกว่าการค้นหาการประกาศที่แตกต่างกันมาก 2. บ่อยครั้งที่คุณต้องเขียนโค้ดน้อยลงเมื่อใช้ฟังก์ชันอินไลน์
Volodymyr


29

คำตอบสำหรับคำถามของคุณคือใช่และไม่ใช่ขึ้นอยู่กับความหมายของ "ฟังก์ชันอินไลน์" หากคุณกำลังใช้คำที่เหมือนกับที่ใช้ในการพัฒนา C ++ คำตอบคือไม่คุณไม่สามารถทำได้แม้แต่นิพจน์แลมบ์ดาก็เป็นการเรียกใช้ฟังก์ชัน แม้ว่าจะเป็นความจริงที่คุณสามารถกำหนดนิพจน์แลมบ์ดาแบบอินไลน์เพื่อแทนที่การประกาศฟังก์ชันใน C # แต่คอมไพเลอร์ยังคงสร้างฟังก์ชันที่ไม่ระบุตัวตน

นี่คือรหัสง่ายๆที่ฉันใช้ในการทดสอบสิ่งนี้ (VS2015):

    static void Main(string[] args)
    {
        Func<int, int> incr = a => a + 1;
        Console.WriteLine($"P1 = {incr(5)}");
    }

คอมไพเลอร์สร้างอะไร ฉันใช้เครื่องมือที่ดีที่เรียกว่า ILSpy ซึ่งแสดงการประกอบ IL จริงที่สร้างขึ้น ดูสิ (ฉันได้ละเว้นการตั้งค่าชั้นเรียนจำนวนมาก)

นี่คือฟังก์ชั่นหลัก:

        IL_001f: stloc.0
        IL_0020: ldstr "P1 = {0}"
        IL_0025: ldloc.0
        IL_0026: ldc.i4.5
        IL_0027: callvirt instance !1 class [mscorlib]System.Func`2<int32, int32>::Invoke(!0)
        IL_002c: box [mscorlib]System.Int32
        IL_0031: call string [mscorlib]System.String::Format(string, object)
        IL_0036: call void [mscorlib]System.Console::WriteLine(string)
        IL_003b: ret

ดูบรรทัดเหล่านั้น IL_0026 และ IL_0027 ไหม คำสั่งทั้งสองนั้นโหลดหมายเลข 5 และเรียกใช้ฟังก์ชัน จากนั้นจัดรูปแบบ IL_0031 และ IL_0036 และพิมพ์ผลลัพธ์

และนี่คือฟังก์ชันที่เรียกว่า:

        .method assembly hidebysig 
            instance int32 '<Main>b__0_0' (
                int32 a
            ) cil managed 
        {
            // Method begins at RVA 0x20ac
            // Code size 4 (0x4)
            .maxstack 8

            IL_0000: ldarg.1
            IL_0001: ldc.i4.1
            IL_0002: add
            IL_0003: ret
        } // end of method '<>c'::'<Main>b__0_0'

มันเป็นฟังก์ชันสั้น ๆ แต่มันคือฟังก์ชัน

นี่คุ้มค่ากับความพยายามในการเพิ่มประสิทธิภาพหรือไม่? ไม่. บางทีถ้าคุณเรียกมันหลายพันครั้งต่อวินาที แต่ถ้าประสิทธิภาพนั้นสำคัญคุณควรพิจารณาเรียกโค้ดเนทีฟที่เขียนด้วย C / C ++ เพื่อทำงาน

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

"การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากเหง้าของความชั่วร้ายทั้งหมด (หรืออย่างน้อยที่สุดก็ส่วนใหญ่) ในการเขียนโปรแกรม" - Donald Knuth

"โปรแกรมที่ทำงานไม่ถูกต้องไม่จำเป็นต้องทำงานเร็ว" - ฉัน


3
นี่เป็นเพลงยอดนิยมของ Google สำหรับฟังก์ชัน C # ที่ฝังไว้ คุณพูดว่า "บางทีถ้าคุณเรียกมันหลายพันครั้งต่อวินาที" และเดาว่าอะไร - ฉันเรียกมันว่า 4 ล้านครั้งต่อวินาทีใช่แล้ว ... ฉันกำลังจะต้องทำ: การคลายฟังก์ชั่นเดียวด้วยตนเองใน 16 ตำแหน่ง)
อดัม

17

ใช่.

คุณสามารถสร้างวิธีการที่ไม่ระบุชื่อหรือนิพจน์แลมบ์ดา :

Func<string, string> PrefixTrimmer = delegate(string x) {
    return x ?? "";
};
Func<string, string> PrefixTrimmer = x => x ?? "";

ขอบคุณสำหรับคำตอบคุณช่วยอธิบายเพิ่มเติมเกี่ยวกับคำตอบได้ไหม lamba one ฉันหมายถึงcodeFunc <string, string> PrefixTrimmer = x => x ?? ""; codeฉันใหม่สำหรับสิ่งนี้และฉันไม่เข้าใจเป็นอย่างดีกับเอกสาร MSDN
Joe Dixon

2
@Joe: x ?? ""เป็นตัวดำเนินการเชื่อมต่อแบบ null msdn.microsoft.com/en-us/library/ms173224.aspx
SLaks

ในx => (something), xเป็นพารามิเตอร์และ(something)เป็น value` ผลตอบแทน
SLaks


2
@Hellfrost: อ่านคำถาม เขาไม่ได้ถามเกี่ยวกับฟังก์ชันอินไลน์สไตล์ C ++ ฉันไม่ต้องการสร้างฟังก์ชันเพราะฟังก์ชันนี้จะไม่ช่วยในส่วนอื่น ๆ ของโปรแกรมของฉัน แต่สิ่งนี้
SLaks

5

คุณสามารถใช้Funcซึ่งห่อหุ้มเมธอดที่มีพารามิเตอร์เดียวและส่งกลับค่าของประเภทที่ระบุโดยพารามิเตอร์ TResult

void Method()
{
    Func<string,string> inlineFunction = source => 
    {
        // add your functionality here
        return source ;
     };


    // call the inline function
   inlineFunction("prefix");
}

4

ไม่เพียง แต่วิธีการ Inside เท่านั้นยังสามารถใช้ในชั้นเรียนได้อีกด้วย

class Calculator
    {
        public static int Sum(int x,int y) => x + y;
        public static Func<int, int, int>  Add = (x, y) => x + y;
        public static Action<int,int> DisplaySum = (x, y) => Console.WriteLine(x + y);
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.