เหตุใดคอมไพลเลอร์ C # จึงไม่ใช่รหัสความผิดปกติที่เมธอดแบบคงที่เรียกใช้เมธอดอินสแตนซ์


110

รหัสต่อไปนี้มีวิธีการแบบคงที่Foo()เรียกวิธีการอินสแตนซ์Bar():

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

คอมไพล์โดยไม่มีข้อผิดพลาด * แต่สร้างข้อยกเว้น runtime binder ที่รันไทม์ การลบพารามิเตอร์ไดนามิกออกเป็นวิธีการเหล่านี้ทำให้เกิดข้อผิดพลาดของคอมไพเลอร์ตามที่คาดไว้

เหตุใดการมีพารามิเตอร์แบบไดนามิกจึงอนุญาตให้คอมไพล์โค้ดได้? ReSharper ไม่แสดงว่าเป็นข้อผิดพลาดเช่นกัน

แก้ไข 1: * ใน Visual Studio 2008

แก้ไข 2:เพิ่มsealedเนื่องจากเป็นไปได้ว่าคลาสย่อยอาจมีBar(...)วิธีการแบบคงที่ แม้แต่เวอร์ชันที่ปิดผนึกก็ยังคอมไพล์เมื่อไม่สามารถเรียกวิธีการอื่นนอกเหนือจากวิธีอินสแตนซ์ที่รันไทม์ได้


8
+1 สำหรับคำถามที่ดีมาก
cuongle

40
นี่คือคำถามของ Eric-Lippert
Olivier Jacot-Descombes

3
ฉันค่อนข้างแน่ใจว่า Jon Skeet จะรู้ว่าจะทำอย่างไรกับสิ่งนี้เช่นกัน;) @ OlivierJacot-Descombes
พัน

2
@ Olivier, Jon Skeet อาจต้องการให้โค้ดคอมไพล์ดังนั้นคอมไพเลอร์จึงอนุญาต :-))
Mike Scott

5
นี่เป็นอีกตัวอย่างหนึ่งของสาเหตุที่คุณไม่ควรใช้dynamicเว้นแต่ว่าคุณจำเป็นจริงๆ
Servy

คำตอบ:


71

UPDATE: ด้านล่างคำตอบที่ถูกเขียนขึ้นในปี 2012 ก่อนที่จะนำ C # 7.3 (พฤษภาคม 2018) มีอะไรใหม่ใน C # 7.3ส่วนที่ปรับปรุงตัวเลือกโอเวอร์โหลดข้อ 1 จะอธิบายว่ากฎการแก้ปัญหาโอเวอร์โหลดมีการเปลี่ยนแปลงอย่างไรเพื่อให้การโอเวอร์โหลดแบบไม่คงที่จะถูกยกเลิกก่อนกำหนด ดังนั้นคำตอบด้านล่าง (และคำถามทั้งหมดนี้) ส่วนใหญ่มี แต่ความสนใจทางประวัติศาสตร์ในตอนนี้!


(ก่อน C # 7.3 :)

ด้วยเหตุผลบางประการความละเอียดเกินพิกัดมักจะหาค่าที่ตรงกันได้ดีที่สุดก่อนที่จะตรวจสอบค่าคงที่และไม่คงที่ โปรดลองใช้รหัสนี้กับประเภทคงที่ทั้งหมด:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

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

นอกจากนี้: ดังนั้นผมคิดว่าคำอธิบายของdynamicตัวอย่างของเดิมคำถามคือว่าเพื่อให้สอดคล้องเมื่อประเภทเป็นแบบไดนามิกเรายังเป็นครั้งแรกหาสิ่งที่ดีที่สุดสำหรับการโอเวอร์โหลด (ตรวจสอบจำนวนและพารามิเตอร์พารามิเตอร์เพียงประเภทอื่น ๆ ไม่คงที่เมื่อเทียบกับที่ไม่ใช่ -static) จากนั้นตรวจสอบค่าคงที่เท่านั้น แต่นั่นหมายความว่าการตรวจสอบแบบคงต้องรอจนกว่ารันไทม์ ดังนั้นพฤติกรรมที่สังเกตได้

นอกจากนี้เวลาที่กำหนด: บางหลังที่ว่าทำไมพวกเขาเลือกที่จะทำสิ่งนี้เพื่อตลกสามารถอนุมานจากการโพสต์บล็อกนี้โดยเอริค Lippert


ไม่มีการโอเวอร์โหลดในคำถามเดิม คำตอบที่แสดงการโอเวอร์โหลดแบบคงที่ไม่เกี่ยวข้อง ไม่ถูกต้องที่จะตอบว่า "ถ้าคุณเขียนสิ่งนี้ ... " เนื่องจากฉันไม่ได้เขียนว่า :-)
Mike Scott

5
@MikeScott ฉันแค่พยายามโน้มน้าวคุณว่าความละเอียดเกินพิกัดใน C # มักจะเป็นเช่นนี้: (1) ค้นหาการจับคู่ที่ดีที่สุดโดยไม่คำนึงถึงคงที่ / ไม่คงที่ (2) ตอนนี้เรารู้แล้วว่าควรใช้โอเวอร์โหลดอะไรจากนั้นตรวจสอบค่าคงที่ ด้วยเหตุนี้เมื่อdynamicถูกนำมาใช้ในภาษาฉันคิดว่านักออกแบบของ C # กล่าวว่า: "เราจะไม่พิจารณา (2) เวลารวบรวมเมื่อมันเป็นdynamicนิพจน์" ดังนั้นจุดประสงค์ของฉันที่นี่คือการคิดว่าทำไมพวกเขาถึงเลือกที่จะไม่ตรวจสอบอินสแตนซ์แบบคงที่กับอินสแตนซ์จนกระทั่งรันไทม์ ผมจะบอกว่าการตรวจสอบนี้เกิดขึ้นที่มีผลผูกพันเวลา
Jeppe Stig Nielsen

พอใช้ แต่ก็ยังไม่อธิบายว่าทำไมในกรณีนี้ที่คอมไพเลอร์ไม่สามารถแก้ไขการเรียกใช้วิธีอินสแตนซ์ได้ กล่าวอีกนัยหนึ่งวิธีที่คอมไพลเลอร์ทำการแก้ปัญหานั้นง่าย - มันไม่รู้จักกรณีง่ายๆเช่นตัวอย่างของฉันซึ่งไม่มีความเป็นไปได้ที่จะไม่สามารถแก้ไขการโทรได้ การประชดคือ: โดยการมี Bar () วิธีเดียวที่มีพารามิเตอร์แบบไดนามิกคอมไพลเลอร์จะละเว้นเมธอด Bar () เดียวนั้น
Mike Scott

45
ฉันเขียนส่วนนี้ของคอมไพเลอร์ C # และ Jeppe ก็พูดถูก กรุณาโหวตเรื่องนี้ ความละเอียดเกินเกิดขึ้นก่อนที่จะตรวจสอบว่าเมธอดที่กำหนดเป็นวิธีการแบบคงที่หรือวิธีอินสแตนซ์และในกรณีนี้เราเลื่อนการแก้ปัญหาโอเวอร์โหลดไปเป็นรันไทม์ดังนั้นการตรวจสอบแบบคงที่ / อินสแตนซ์จนถึงรันไทม์ด้วยเช่นกัน นอกจากนี้คอมไพเลอร์ยังใช้ "ความพยายามอย่างเต็มที่" ในการค้นหาข้อผิดพลาดแบบไดนามิกแบบคงที่ซึ่งไม่ครอบคลุมทั้งหมด
Chris Burrows

30

Foo มีพารามิเตอร์ "x" ที่เป็นไดนามิกซึ่งหมายความว่า Bar (x) เป็นนิพจน์แบบไดนามิก

เป็นไปได้อย่างสมบูรณ์แบบสำหรับตัวอย่างที่จะมีวิธีการเช่น:

static Bar(SomeType obj)

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


14
แต่เมื่อคุณใช้เมธอดอินสแตนซ์บาร์มันจะไม่รวบรวมอีกต่อไป
Justin Harvey

1
@ จัสตินน่าสนใจ - คำเตือน? หรือข้อผิดพลาด? ไม่ว่าจะด้วยวิธีใดก็สามารถตรวจสอบความถูกต้องได้เฉพาะกลุ่มวิธีการเท่านั้นโดยปล่อยให้ความละเอียดโอเวอร์โหลดเต็มไปยังรันไทม์
Marc Gravell

1
@ มาร์คเนื่องจากไม่มีวิธี Bar () อื่นคุณจึงไม่ตอบคำถาม คุณช่วยอธิบายสิ่งนี้ได้ไหมว่ามี Bar () เพียงวิธีเดียวที่ไม่มีการโอเวอร์โหลด? ทำไมต้องเลื่อนไปที่รันไทม์ในเมื่อไม่มีวิธีอื่นใดที่สามารถเรียกได้ หรือมี? หมายเหตุ: ฉันได้แก้ไขโค้ดเพื่อปิดคลาสซึ่งยังคงคอมไพล์อยู่
Mike Scott

1
@mike ว่าทำไมถึงเลื่อนไปที่รันไทม์: เพราะนั่นคือความหมาย
Marc Gravell

2
@ ไมค์เป็นไปไม่ได้ไม่ใช่ประเด็น; สิ่งที่สำคัญคือจำเป็นหรือไม่ จุดทั้งหมดที่มีไดนามิกไม่ใช่หน้าที่ของคอมไพเลอร์
Marc Gravell

9

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

เมธอด "right" จะถูกกำหนดระหว่างรันไทม์ คอมไพลเลอร์ไม่สามารถทราบได้ว่ามีวิธีการที่ถูกต้องในระหว่างรันไทม์หรือไม่

คีย์เวิร์ด "ไดนามิก" ถูกกำหนดสำหรับไดนามิกและภาษาสคริปต์โดยที่เมธอดสามารถกำหนดได้ตลอดเวลาแม้ในระหว่างรันไทม์ ของบ้า

นี่คือตัวอย่างที่จัดการ ints แต่ไม่มีสตริงเนื่องจากวิธีการอยู่บนอินสแตนซ์

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

คุณสามารถเพิ่มวิธีจัดการการโทรที่ "ผิด" ทั้งหมดซึ่งไม่สามารถจัดการได้

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

รหัสการโทรในตัวอย่างของคุณไม่ควรเป็น Example.Bar (... ) แทนที่จะเป็น Example.Foo (... )? Foo () ไม่เกี่ยวข้องกับตัวอย่างของคุณหรือ? ฉันไม่เข้าใจตัวอย่างของคุณจริงๆ เหตุใดการเพิ่มวิธีการทั่วไปแบบคงที่จึงทำให้เกิดปัญหา คุณสามารถแก้ไขคำตอบของคุณเพื่อรวมวิธีการนั้นแทนการให้เป็นตัวเลือกได้หรือไม่?
Mike Scott

แต่ตัวอย่างที่ฉันโพสต์มีเพียงวิธีการอินสแตนซ์เดียวและไม่มีการโอเวอร์โหลดดังนั้นในเวลาคอมไพล์คุณจะรู้ว่าไม่มีวิธีการคงที่ที่เป็นไปได้ที่สามารถแก้ไขได้ เฉพาะในกรณีที่คุณเพิ่มอย่างน้อยหนึ่งอย่างสถานการณ์จะเปลี่ยนแปลงและรหัสถูกต้อง
Mike Scott

แต่ตัวอย่างนี้ยังมีวิธี Bar () มากกว่าหนึ่งวิธี ตัวอย่างของฉันมีเพียงวิธีเดียว ดังนั้นจึงไม่มีความเป็นไปได้ที่จะเรียกเมธอด Static Bar () ใด ๆ การโทรสามารถแก้ไขได้ในเวลาคอมไพล์
Mike Scott

@ ไมค์ก็ได้! = is; ด้วยไดนามิกจึงไม่จำเป็นต้องทำเช่นนั้น
Marc Gravell

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