เหตุใดจึงไม่สามารถกำหนดวิธีที่ไม่ระบุชื่อให้ var ได้


139

ฉันมีรหัสต่อไปนี้:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

อย่างไรก็ตามสิ่งต่อไปนี้ไม่ได้รวบรวม:

var comparer = delegate(string value) {
    return value != "0";
};

ทำไมคอมไพเลอร์ไม่สามารถคิดได้ว่ามันคือFunc<string, bool>อะไร? ใช้พารามิเตอร์สตริงเดียวและส่งคืนบูลีน แต่มันทำให้ฉันมีข้อผิดพลาด:

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

ฉันมีหนึ่งเดาและนั่นคือถ้ารุ่น var รวบรวมมันจะขาดความมั่นคงถ้าฉันมีต่อไปนี้:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

ข้างต้นไม่สมเหตุสมผลเนื่องจาก Func <> อนุญาตได้เพียง 4 อาร์กิวเมนต์เท่านั้น (ใน. NET 3.5 ซึ่งเป็นสิ่งที่ฉันใช้) บางทีใครบางคนสามารถชี้แจงปัญหา ขอบคุณ


3
หมายเหตุเกี่ยวกับอาร์กิวเมนต์ 4 อาร์กิวเมนต์ของคุณใน. NET 4 Func<>ยอมรับอาร์กิวเมนต์ได้สูงสุด 16 รายการ
Anthony Pegram

ขอขอบคุณสำหรับการชี้แจง. ฉันใช้. NET 3.5
Marlon

9
ทำไมจะทำให้คอมไพเลอร์คิดว่าเป็นFunc<string, bool>? มันดูเหมือนConverter<string, bool>กับฉัน!
Ben Voigt


3
บางครั้งฉันก็พลาด VB ..Dim comparer = Function(value$) value <> "0"
Slai

คำตอบ:


155

คนอื่น ๆ ได้ชี้ให้เห็นแล้วว่ามีประเภทตัวแทนที่เป็นไปได้มากมายซึ่งคุณอาจหมายถึง สิ่งที่เป็นเรื่องเกี่ยวกับพิเศษเพื่อFuncว่ามันสมควรที่จะเริ่มต้นแทนPredicateหรือActionหรือความเป็นไปได้อื่น ๆ ? และสำหรับลูกแกะทำไมเห็นได้ชัดว่าความตั้งใจที่จะเลือกรูปแบบตัวแทนมากกว่ารูปแบบต้นไม้แสดงออก?

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

var x1 = (ref int y)=>123;

ไม่มีFunc<T>ประเภทที่จะอ้างอิงอะไร

var x2 = y=>123;

เราไม่ทราบประเภทของพารามิเตอร์ที่เป็นทางการแม้ว่าเราจะทราบถึงการคืนสินค้า (หรือเราหรือไม่ผลตอบแทน int หรือไม่สั้นสั้นหรือไม่ไบต์)

var x3 = (int y)=>null;

เราไม่ทราบประเภทการคืนสินค้า แต่ไม่สามารถถือเป็นโมฆะได้ ชนิดส่งคืนอาจเป็นชนิดการอ้างอิงหรือชนิดค่า nullable ใด ๆ

var x4 = (int y)=>{ throw new Exception(); }

อีกครั้งเราไม่ทราบประเภทการคืนสินค้าและคราวนี้มันอาจเป็นโมฆะ

var x5 = (int y)=> q += y;

นั่นเป็นเจตนาที่จะเป็นโมฆะ - กลับคำสั่งแลมบ์ดาหรือสิ่งที่ส่งกลับค่าที่ได้รับมอบหมายให้ q? ทั้งสองถูกกฎหมาย ซึ่งเราควรเลือก

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

สถานการณ์ที่เป็นประโยชน์จริง ๆ คือ:

var xAnon = (int y)=>new { Y = y };

เพราะไม่มีประเภท "พูด" สำหรับสิ่งนั้น แต่เรามีปัญหานี้อยู่ตลอดเวลาและเราแค่ใช้การอนุมานประเภทเมธอดเพื่ออนุมานประเภท:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

และตอนนี้การอนุมานประเภทเมธอดจะหาว่าประเภท func คืออะไร


43
คุณจะรวบรวมคำตอบ SO ของคุณลงในหนังสือเมื่อใด ฉันจะซื้อมัน :)
Matt Greer

13
ฉันสองข้อเสนอสำหรับหนังสือ Eric Lippert ของคำตอบดังนั้น ชื่อที่แนะนำ: "Reflections From The Stack"
Adam Rackis

24
@ อีริค: คำตอบที่ดี แต่มันก็ทำให้เข้าใจผิดเล็กน้อยเพื่อแสดงให้เห็นว่านี่เป็นสิ่งที่เป็นไปไม่ได้เพราะมันใช้งานได้ดีจริง ๆ ใน D. มันแค่ว่าพวกคุณไม่ได้เลือกที่จะมอบตัวแทนของพวกเขา บนบริบทของพวกเขา ... ดังนั้น IMHO คำตอบควรเป็น "เพราะนั่นคือวิธีที่เราทำ" มากกว่าสิ่งอื่นใด :)
user541686

5
@abstractdissonance ฉันยังทราบว่าคอมไพเลอร์เป็นโอเพนซอร์ส หากคุณสนใจคุณสมบัตินี้คุณสามารถบริจาคเวลาและความพยายามในการทำให้เกิดขึ้นได้ ฉันแนะนำให้คุณส่งคำขอดึง
Eric Lippert

7
@AbstractDissonance: เราวัดค่าใช้จ่ายในแง่ของทรัพยากรที่ จำกัด : นักพัฒนาและเวลา ความรับผิดชอบนี้ไม่ได้รับจากพระเจ้า มันถูกกำหนดโดยรองประธานฝ่ายพัฒนา แนวคิดที่ว่าทีมงาน C # สามารถเพิกเฉยต่อกระบวนการงบประมาณเป็นเรื่องแปลก ฉันขอยืนยันกับคุณว่าการแลกเปลี่ยนกันและยังคงเกิดขึ้นจากการพิจารณาอย่างรอบคอบของผู้เชี่ยวชาญที่ชุมชน C # แสดงความปรารถนาภารกิจเชิงกลยุทธ์สำหรับ Microsoft และรสนิยมการออกแบบที่ยอดเยี่ยมของพวกเขาเอง
Eric Lippert

29

มีเพียง Eric Lippert เท่านั้นที่รู้ แต่ฉันคิดว่าเป็นเพราะลายเซ็นประเภทตัวแทนไม่ได้กำหนดประเภทเฉพาะ

พิจารณาตัวอย่างของคุณ:

var comparer = delegate(string value) { return value != "0"; };

ต่อไปนี้เป็นข้อสรุปที่เป็นไปได้สองประการสำหรับสิ่งที่varควรเป็น:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

คอมไพเลอร์ตัวใดที่ควรอนุมาน ไม่มีเหตุผลที่ดีที่จะเลือกอย่างใดอย่างหนึ่ง และถึงแม้ว่า a Predicate<T>จะเทียบเท่ากับ a Func<T, bool>แต่มันก็ยังคงเป็นประเภทที่แตกต่างกันในระดับของระบบประเภท. NET คอมไพเลอร์จึงไม่สามารถแก้ไขประเภทผู้รับมอบสิทธิ์ได้อย่างชัดเจนและจะต้องไม่อนุมานประเภท


1
ฉันแน่ใจว่าคนอื่น ๆ ที่ Microsoft ทราบค่อนข้างแน่ใจเช่นกัน ;) แต่ใช่คุณหมายถึงเหตุผลหลักประเภทเวลารวบรวมไม่สามารถกำหนดได้เพราะไม่มี ส่วนที่ 8.5.1 ของข้อกำหนดทางภาษาเน้นเหตุผลนี้เป็นพิเศษสำหรับการไม่อนุญาตให้ใช้ฟังก์ชันที่ไม่ระบุชื่อจากการใช้ในการประกาศตัวแปรที่พิมพ์โดยปริยาย
Anthony Pegram

3
อ๋อ และยิ่งแย่ไปกว่าสำหรับลูกแกะเรายังไม่รู้ด้วยซ้ำว่าเป็นประเภทตัวแทนหรือไม่ มันอาจเป็นต้นไม้แสดงอารมณ์
Eric Lippert

สำหรับผู้ที่สนใจฉันเขียนขึ้นเล็กน้อยเกี่ยวกับเรื่องนี้และวิธีการ C # และ F # วิธีการที่แตกต่างที่mindscapehq.com/blog/index.php/2011/02/23/…
itowlson

ทำไมคอมไพเลอร์ไม่สามารถสร้างรูปแบบใหม่ที่ไม่เหมือนใครเช่น C ++ สำหรับฟังก์ชั่นแลมบ์ดาได้
Weipeng L

พวกเขาจะแตกต่าง "ที่ระดับของระบบประเภท. NET" ได้อย่างไร?
arao6 6

6

เอริค Lippert มีเก่าโพสต์เกี่ยวกับเรื่องที่เขาพูดว่า

และในความเป็นจริงข้อกำหนด C # 2.0 เรียกสิ่งนี้ว่า นิพจน์กลุ่มของเมธอดและนิพจน์เมธอดแบบไม่ระบุชื่อเป็นนิพจน์ที่ไม่มีการพิมพ์ใน C # 2.0 และนิพจน์แลมบ์ดาเข้าร่วมใน C # 3.0 ดังนั้นจึงเป็นเรื่องผิดกฎหมายสำหรับพวกเขาที่จะปรากฏ "เปลือยกาย" ทางด้านขวามือของการประกาศโดยนัย


และนี่คือขีดเส้นใต้ตามมาตรา 8.5.1 ของข้อกำหนดภาษา "นิพจน์ initializer ต้องมีชนิดเวลาคอมไพล์" เพื่อใช้สำหรับตัวแปรโลคัลที่พิมพ์โดยนัย
Anthony Pegram

5

ผู้ได้รับมอบหมายที่แตกต่างกันถือเป็นประเภทที่แตกต่างกัน เช่นActionแตกต่างจากMethodInvokerและActionไม่สามารถกำหนดอินสแตนซ์ของตัวแปรประเภทMethodInvokerได้

ดังนั้นให้ผู้แทนไม่ระบุชื่อ (หรือแลมบ์ดา) เหมือน() => {}มันเป็นActionหรือMethodInvoker? คอมไพเลอร์ไม่สามารถบอกได้

ในทำนองเดียวกันถ้าฉันประกาศประเภทผู้รับมอบสิทธิ์ที่stringโต้แย้งและคืนboolค่าคอมไพเลอร์จะรู้ได้อย่างไรว่าคุณต้องการFunc<string, bool>ประเภทตัวแทนของฉันจริง ๆ ไม่สามารถอนุมานประเภทตัวแทนได้


2

จุดต่อไปนี้มาจาก MSDN เกี่ยวกับตัวแปรท้องถิ่นที่พิมพ์โดยนัย:

  1. var สามารถใช้ได้เฉพาะเมื่อมีการประกาศตัวแปรท้องถิ่นและเริ่มต้นในคำสั่งเดียวกัน ไม่สามารถกำหนดค่าเริ่มต้นให้เป็นโมฆะหรือกลุ่มวิธีการหรือฟังก์ชันที่ไม่ระบุชื่อ
  2. คำหลัก var สั่งให้คอมไพเลอร์อนุมานชนิดของตัวแปรจากนิพจน์ทางด้านขวาของคำสั่งการเริ่มต้น
  3. สิ่งสำคัญคือต้องเข้าใจว่าคำหลัก var ไม่ได้หมายถึง "ตัวแปร" และไม่ได้ระบุว่าตัวแปรนั้นถูกพิมพ์อย่างหลวม ๆ หรือถูกผูกมัดล่าช้า มันก็หมายความว่าคอมไพเลอร์กำหนดและกำหนดประเภทที่เหมาะสมที่สุด

การอ้างอิง MSDN: ตัวแปรท้องถิ่นที่พิมพ์โดยนัย

พิจารณาสิ่งต่อไปนี้เกี่ยวกับวิธีการไม่ระบุชื่อ:

  1. วิธีการที่ไม่ระบุชื่อช่วยให้คุณสามารถละเว้นรายการพารามิเตอร์

การอ้างอิง MSDN: วิธีการไม่ระบุชื่อ

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


1

โพสต์ของฉันไม่ตอบคำถามจริง แต่ตอบคำถามพื้นฐานของ:

"ฉันจะหลีกเลี่ยงการพิมพ์บางอย่างที่น่าเกลียดได้Func<string, string, int, CustomInputType, bool, ReturnType>อย่างไร" [1]

ในฐานะที่เป็นโปรแกรมเมอร์ขี้เกียจ / แฮ็คที่ฉันเป็นฉันได้ทดลองใช้โดยใช้Func<dynamic, object>พารามิเตอร์อินพุตเดี่ยวและส่งคืนวัตถุ

สำหรับหลาย ๆ อาร์กิวเมนต์คุณสามารถใช้มันได้เช่น:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

เคล็ดลับ: คุณสามารถใช้Action<dynamic>หากคุณไม่ต้องการส่งคืนวัตถุ

ใช่ฉันรู้ว่ามันอาจจะขัดกับหลักการเขียนโปรแกรมของคุณ แต่มันสมเหตุสมผลสำหรับฉัน

ฉันเป็นสามเณรที่ได้รับมอบหมาย ... แค่อยากจะแบ่งปันสิ่งที่ฉันเรียนรู้


[1]นี่ถือว่าคุณไม่ได้เรียกวิธีการที่ต้องใช้Funcพารามิเตอร์ที่กำหนดไว้ล่วงหน้าซึ่งในกรณีนี้คุณจะต้องพิมพ์สตริงที่ต้องการ: /


0

เป็นอย่างไรบ้าง?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

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