Func คืออะไรใช้อย่างไรและเมื่อใด


115

มันคืออะไรFunc<>และใช้ทำอะไร?


4
เป็นเพียงทางลัดสำหรับผู้รับมอบสิทธิ์ที่มีลายเซ็นเฉพาะ เพื่อให้เข้าใจคำตอบด้านล่างนี้อย่างเต็มที่คุณจะต้องเข้าใจผู้ได้รับมอบหมาย ;-)
Theo Lenndorff

2
ในคำตอบของ @Oded ระบุว่าIf you have a function that needs to return different types, depending on the parameters, you can use a Func delegate, specifying the return type.
LCJ

คำตอบ:


76

Func<T>Tเป็นชนิดที่ผู้ร่วมประชุมที่กำหนดไว้ล่วงหน้าสำหรับวิธีการที่จะส่งกลับค่าของชนิดบาง

กล่าวอีกนัยหนึ่งคุณสามารถใช้ประเภทนี้เพื่ออ้างอิงวิธีการที่ส่งคืนค่าบางส่วนของT. เช่น

public static string GetMessage() { return "Hello world"; }

อาจมีการอ้างอิงเช่นนี้

Func<string> f = GetMessage;

แต่มันยังสามารถแสดงถึงฟังก์ชันอาร์กิวเมนต์แบบคงที่ =)
Ark-kun

2
@ อาร์คคุงไม่ถูกต้อง คำจำกัดความของFunc<T>คือdelegate TResult Func<out TResult>(). ไม่มีข้อโต้แย้ง Func<T1, T2>จะเป็นฟังก์ชันที่ใช้อาร์กิวเมนต์เดียว
Brian Rasmussen

4
ไม่ฉันถูกต้อง static int OneArgFunc(this string i) { return 42; } Func<int> f = "foo".OneArgFunc;. =)
Ark-kun

1
นั่นเป็นวิธีการขยายที่พิเศษ
Brian Rasmussen

สิ่งที่พิเศษเพียงอย่างเดียวเกี่ยวกับมันคือExtensionแอตทริบิวต์ซึ่งอ่านโดยคอมไพเลอร์ C # / VB.Net เท่านั้นไม่ใช่ CLR โดยทั่วไปวิธีการของอินสแตนซ์ (ซึ่งแตกต่างจากฟังก์ชันคงที่) จะมีพารามิเตอร์ 0th "this" ซ่อนอยู่ ดังนั้นวิธีการอินสแตนซ์ 1 อาร์กิวเมนต์จึงคล้ายกับฟังก์ชันคงที่ 2 อาร์กิวเมนต์ จากนั้นเราจะมีผู้เข้าร่วมประชุมซึ่งจัดเก็บเป้าหมายวัตถุและฟังก์ชั่นตัวชี้ ผู้รับมอบสิทธิ์สามารถจัดเก็บอาร์กิวเมนต์แรกในเป้าหมายหรือไม่ทำเช่นนั้น
Ark-kun

87

คิดว่าเป็นตัวยึด จะมีประโยชน์มากเมื่อคุณมีโค้ดที่เป็นไปตามรูปแบบที่กำหนด แต่ไม่จำเป็นต้องเชื่อมโยงกับฟังก์ชันเฉพาะใด ๆ

ตัวอย่างเช่นพิจารณาEnumerable.Selectวิธีการขยาย

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

วิธีนี้ใช้Func<T, TResult>แทนฟังก์ชันคอนกรีตใด ๆ สิ่งนี้ช่วยให้สามารถใช้ในบริบทใด ๆที่ใช้รูปแบบข้างต้น

ตัวอย่างเช่นสมมติว่าฉันมีList<Person>และฉันต้องการเพียงแค่ชื่อของทุกคนในรายการ ฉันสามารถทำได้:

var names = people.Select(p => p.Name);

หรือบอกว่าฉันต้องการอายุของทุกคน:

var ages = people.Select(p => p.Age);

ทันทีคุณจะเห็นว่าฉันสามารถใช้ประโยชน์จากรหัสเดียวกันที่แสดงรูปแบบ (ด้วยSelect) ด้วยฟังก์ชันที่แตกต่างกันสองฟังก์ชัน ( p => p.Nameและp => p.Age) ได้อย่างไร

อีกทางเลือกหนึ่งคือเขียนเวอร์ชันอื่นSelectทุกครั้งที่คุณต้องการสแกนลำดับเพื่อหาค่าประเภทอื่น ดังนั้นเพื่อให้ได้ผลเช่นเดียวกับข้างต้นฉันจะต้อง:

// Presumably, the code inside these two methods would look almost identical;
// the only difference would be the part that actually selects a value
// based on a Person.
var names = GetPersonNames(people);
var ages = GetPersonAges(people);

ด้วยตัวแทนที่ทำหน้าที่เป็นตัวยึดตำแหน่งฉันจึงปลดปล่อยตัวเองจากการเขียนรูปแบบเดิมซ้ำแล้วซ้ำเล่าในกรณีเช่นนี้


66

Func<T1, T2, ..., Tn, Tr> แสดงถึงฟังก์ชันที่ใช้อาร์กิวเมนต์ (T1, T2, ... , Tn) และส่งกลับ Tr

ตัวอย่างเช่นหากคุณมีฟังก์ชัน:

double sqr(double x) { return x * x; }

คุณสามารถบันทึกเป็นตัวแปรฟังก์ชัน:

Func<double, double> f1 = sqr;
Func<double, double> f2 = x => x * x;

จากนั้นใช้เหมือนกับที่คุณใช้ sqr:

f1(2);
Console.WriteLine(f2(f1(4)));

เป็นต้น

โปรดจำไว้ว่าเป็นผู้รับมอบสิทธิ์สำหรับข้อมูลขั้นสูงเพิ่มเติมโปรดดูเอกสารประกอบ


1
คำตอบที่ยอดเยี่ยม แต่สำหรับการรวบรวมคีย์เวิร์ด static is neeed
boctulus

16

ฉันพบว่าFunc<T>มีประโยชน์มากเมื่อฉันสร้างส่วนประกอบที่จำเป็นต้องปรับให้เหมาะกับแต่ละบุคคล "ทันที"

ยกตัวอย่างง่ายๆนี้: PrintListToConsole<T>ส่วนประกอบ

อ็อบเจ็กต์ง่ายๆที่พิมพ์รายการอ็อบเจ็กต์นี้ไปยังคอนโซล คุณต้องการให้นักพัฒนาที่ใช้มันปรับแต่งผลลัพธ์

ตัวอย่างเช่นคุณต้องการให้เขากำหนดรูปแบบตัวเลขประเภทใดประเภทหนึ่งเป็นต้น

ไม่มี Func

ขั้นแรกคุณต้องสร้างอินเทอร์เฟซสำหรับคลาสที่รับอินพุตและสร้างสตริงเพื่อพิมพ์ไปยังคอนโซล

interface PrintListConsoleRender<T> {
  String Render(T input);
}

จากนั้นคุณต้องสร้างคลาสPrintListToConsole<T>ที่ใช้อินเทอร์เฟซที่สร้างไว้ก่อนหน้านี้และใช้กับแต่ละองค์ประกอบของรายการ

class PrintListToConsole<T> {

    private PrintListConsoleRender<T> _renderer;

    public void SetRenderer(PrintListConsoleRender<T> r) {
        // this is the point where I can personalize the render mechanism
        _renderer = r;
    }

    public void PrintToConsole(List<T> list) {
        foreach (var item in list) {
            Console.Write(_renderer.Render(item));
        }
    }   
}

นักพัฒนาที่จำเป็นต้องใช้ส่วนประกอบของคุณจะต้อง:

  1. ใช้อินเทอร์เฟซ

  2. ผ่านชั้นเรียนจริงไปยัง PrintListToConsole

    class MyRenderer : PrintListConsoleRender<int> {
        public String Render(int input) {
            return "Number: " + input;
        }
    }
    
    class Program {
        static void Main(string[] args) {
            var list = new List<int> { 1, 2, 3 };
            var printer = new PrintListToConsole<int>();
            printer.SetRenderer(new MyRenderer());
            printer.PrintToConsole(list);
            string result = Console.ReadLine();   
        }   
    }

การใช้ Func นั้นง่ายกว่ามาก

ภายในคอมโพเนนต์คุณกำหนดพารามิเตอร์ประเภทFunc<T,String>ที่แสดงถึงอินเทอร์เฟซของฟังก์ชันที่รับพารามิเตอร์อินพุตประเภท T และส่งคืนสตริง (เอาต์พุตสำหรับคอนโซล)

class PrintListToConsole<T> {

    private Func<T, String> _renderFunc;

    public void SetRenderFunc(Func<T, String> r) {
        // this is the point where I can set the render mechanism
        _renderFunc = r;
    }

    public void Print(List<T> list) {
        foreach (var item in list) {
            Console.Write(_renderFunc(item));
        }
    }
}

เมื่อนักพัฒนาใช้คอมโพเนนต์ของคุณเขาก็ส่งผ่านไปยังคอมโพเนนต์การใช้งานFunc<T, String>ประเภทนั่นคือฟังก์ชันที่สร้างเอาต์พุตสำหรับคอนโซล

class Program {
    static void Main(string[] args) {
        var list = new List<int> { 1, 2, 3 }; // should be a list as the method signature expects
        var printer = new PrintListToConsole<int>();
        printer.SetRenderFunc((o) => "Number:" + o);
        printer.Print(list); 
        string result = Console.ReadLine();
    }
}

Func<T>ช่วยให้คุณกำหนดอินเทอร์เฟซวิธีการทั่วไปได้ทันที คุณกำหนดประเภทของอินพุตและประเภทของเอาต์พุต เรียบง่ายและกระชับ


2
ขอบคุณสำหรับการโพสต์ Marco นี้ จะได้ช่วยจริงๆฉัน. ฉันพยายามทำความเข้าใจ func มาระยะหนึ่งแล้วและยังใช้มันอย่างแข็งขันในการเขียนโปรแกรมด้วย ตัวอย่างนี้จะล้างเส้นทาง ฉันต้องเพิ่มวิธี StampaFunc เนื่องจากที่เหลืออยู่ในรหัสดั้งเดิมซึ่งทำให้ไม่สามารถแสดงได้
Siwoku Adeola

1
ฉันคิดว่ามีสายพลาดในตัวอย่าง Func การเรียกใช้ฟังก์ชันการพิมพ์หรือ StampaFunc อยู่ที่ไหน
Bashar Abu Shamaa

11

Func<T1,R>และอื่น ๆ ที่กำหนดไว้ล่วงหน้าทั่วไปFuncที่ได้รับมอบหมาย ( Func<T1,T2,R>, Func<T1,T2,T3,R>และอื่น ๆ ) เป็นผู้ได้รับมอบหมายทั่วไปที่ส่งกลับชนิดของพารามิเตอร์ทั่วไปที่ผ่านมา

หากคุณมีฟังก์ชันที่ต้องการส่งคืนประเภทต่างๆขึ้นอยู่กับพารามิเตอร์คุณสามารถใช้Funcผู้รับมอบสิทธิ์โดยระบุประเภทการส่งคืน


7

เป็นเพียงผู้รับมอบสิทธิ์ทั่วไปที่กำหนดไว้ล่วงหน้า การใช้งานคุณไม่จำเป็นต้องประกาศผู้รับมอบสิทธิ์ทุกคน มีผู้รับมอบสิทธิ์ที่กำหนดไว้ล่วงหน้าอีกรายหนึ่งAction<T, T2...>ซึ่งเหมือนกัน แต่กลับเป็นโมฆะ


0

อาจจะยังไม่สายเกินไปที่จะเพิ่มข้อมูลบางอย่าง

ผลรวม:

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

ศัพท์เฉพาะและ how2use:

Func<input_1, input_2, ..., input1_6, output> funcDelegate = someMethod;

ความหมาย:

public delegate TResult Func<in T, out TResult>(T arg);

ที่ใช้:

ใช้ในนิพจน์แลมบ์ดาและวิธีการที่ไม่ระบุตัวตน

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