Tại sao trình biên dịch C # không mã lỗi trong đó một phương thức tĩnh gọi một phương thức thể hiện?


110

Các mã sau đây có một phương pháp tĩnh, Foo()gọi một phương pháp dụ, Bar():

public sealed class Example
{
    int count;

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

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

Nó biên dịch mà không có lỗi * nhưng tạo ra một ngoại lệ chất kết dính thời gian chạy trong thời gian chạy. Loại bỏ tham số động đối với các phương thức này gây ra lỗi trình biên dịch, như mong đợi.

Vậy tại sao có một tham số động lại cho phép mã được biên dịch? ReSharper cũng không hiển thị nó là một lỗi.

Chỉnh sửa 1: * trong Visual Studio 2008

Chỉnh sửa 2: được thêm vào sealedvì có thể một lớp con có thể chứa một Bar(...)phương thức tĩnh . Ngay cả phiên bản niêm phong cũng biên dịch khi không thể gọi bất kỳ phương thức nào khác với phương thức phiên bản trong thời gian chạy.


8
1 cho câu hỏi rất tốt
Cuongle

40
Đây là một câu hỏi của Eric-Lippert.
Olivier Jacot-Descombes 11/10/12

3
Tôi khá chắc chắn Jon Skeet sẽ biết phải làm gì với là tốt này tho;) @ OlivierJacot-Descombes
Thousand

2
@Olivier, Jon Skeet lẽ muốn mã để biên dịch, vì vậy trình biên dịch cho phép nó :-))
Mike Scott

5
Đây là một ví dụ khác về lý do tại sao bạn không nên sử dụng dynamictrừ khi bạn thực sự cần.
Phục vụ

Câu trả lời:


71

CẬP NHẬT: Câu trả lời dưới đây được viết vào năm 2012, trước khi C # 7.3 ra đời (tháng 5 năm 2018) . Trong Có gì mới trong C # 7.3 , phần Cải thiện ứng cử viên quá tải , mục 1, nó được giải thích cách các quy tắc giải quyết quá tải đã thay đổi để các quá tải không tĩnh bị loại bỏ sớm. Vì vậy, câu trả lời dưới đây (và toàn bộ câu hỏi này) hầu như chỉ có lợi ích lịch sử cho đến nay!


(Pre C # 7.3 :)

Vì một số lý do, giải quyết quá tải luôn tìm thấy kết quả phù hợp nhất trước khi kiểm tra tĩnh so với không tĩnh. Vui lòng thử mã này với tất cả các loại tĩnh:

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

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

Điều này sẽ không biên dịch bởi vì quá tải tốt nhất là một string. Nhưng này, đó là một phương thức thể hiện, vì vậy trình biên dịch phàn nàn (thay vì lấy quá tải tốt thứ hai).

Bổ sung: Vì vậy, tôi nghĩ giải thích về dynamicví dụ của Câu hỏi ban đầu là, để nhất quán, khi các kiểu là động, trước tiên chúng ta cũng phải tìm quá tải tốt nhất (chỉ kiểm tra số tham số và kiểu tham số, v.v., không phải tĩnh so với không -static), và chỉ sau đó kiểm tra tĩnh. Nhưng điều đó có nghĩa là kiểm tra tĩnh phải đợi cho đến thời gian chạy. Do đó hành vi quan sát được.

Bổ sung muộn: Một số cơ sở về lý do tại sao họ chọn làm những việc theo thứ tự vui nhộn này có thể được suy ra từ bài đăng blog này của Eric Lippert .


Không có quá tải trong câu hỏi ban đầu. Các câu trả lời hiển thị quá tải tĩnh không liên quan. Đó là không hợp lệ để trả lời "tốt nếu bạn là người viết ..." kể từ khi tôi đã không viết rằng :-)
Mike Scott

5
@MikeScott Tôi chỉ cố gắng thuyết phục bạn rằng độ phân giải quá tải trong C # luôn diễn ra như thế này: (1) Tìm kết quả phù hợp nhất bỏ qua tĩnh / không tĩnh. (2) Bây giờ chúng ta biết những gì quá tải để sử dụng, sau đó kiểm tra xem có tĩnh không. Bởi vì điều này, khi dynamicđược giới thiệu bằng ngôn ngữ này, tôi nghĩ rằng các nhà thiết kế của C # đã nói: "Chúng tôi sẽ không xem xét (2) thời gian biên dịch khi đó là một dynamicbiểu thức." Vì vậy, mục đích của tôi ở đây là đưa ra ý tưởng tại sao họ chọn không kiểm tra phiên bản tĩnh so với phiên bản cho đến thời gian chạy. Tôi sẽ nói, việc kiểm tra này xảy ra vào thời điểm ràng buộc .
Jeppe Stig Nielsen,

đủ công bằng nhưng nó vẫn không giải thích tại sao trong trường hợp này mà trình biên dịch không thể giải quyết cuộc gọi đến phương thức cá thể. Nói cách khác, cách trình biên dịch thực hiện giải quyết rất đơn giản - nó không nhận ra trường hợp đơn giản như ví dụ của tôi, nơi không có khả năng nó không thể giải quyết cuộc gọi. Điều trớ trêu là: bằng cách có một phương thức Bar () với một tham số động, trình biên dịch sau đó bỏ qua phương thức Bar () đơn đó.
Mike Scott

45
Tôi đã viết phần này của trình biên dịch C #, và Jeppe đã đúng. Hãy bình chọn này. Giải quyết quá tải xảy ra trước khi kiểm tra xem một phương thức đã cho là phương thức tĩnh hay phương thức cá thể, và trong trường hợp này, chúng tôi trì hoãn giải quyết quá tải cho thời gian chạy và do đó cũng kiểm tra tĩnh / cá thể cho đến thời gian chạy. Ngoài ra, trình biên dịch cố gắng hết sức để tìm ra các lỗi động hoàn toàn không toàn diện.
Chris Burrows

30

Foo có một tham số "x" là động, có nghĩa là Bar (x) là một biểu thức động.

Ví dụ hoàn toàn có thể có các phương thức như:

static Bar(SomeType obj)

Trong trường hợp đó, phương pháp đúng sẽ được giải quyết, vì vậy câu lệnh Bar (x) là hoàn toàn hợp lệ. Thực tế là có một phương thức thể hiện Bar (x) là không thể thay đổi và thậm chí không được xem xét: theo định nghĩa , vì Bar (x) là một biểu thức động, chúng tôi đã hoãn phân giải thành thời gian chạy.


14
nhưng khi bạn lấy ra phương thức instance Bar, nó không còn biên dịch nữa.
Justin Harvey

1
@Justin thú vị - một cảnh báo? Hay một lỗi? Dù bằng cách nào, nó có thể chỉ xác thực trong phạm vi nhóm phương thức, để lại độ phân giải quá tải đầy đủ cho thời gian chạy.
Marc Gravell

1
@Marc, vì không có phương thức Bar () nào khác, bạn sẽ không trả lời câu hỏi. Bạn có thể giải thích điều này vì chỉ có một phương thức Bar () không có quá tải không? Tại sao phải trì hoãn thời gian chạy khi không có cách nào khác có thể gọi được phương thức này? Hay là có? Lưu ý: Tôi đã chỉnh sửa mã để niêm phong lớp, lớp vẫn đang biên dịch.
Mike Scott

1
@mike về lý do tại sao trì hoãn thời gian chạy: bởi vì đó là ý nghĩa của
Marc Gravell

2
@Mike không thể không phải là vấn đề; điều quan trọng là liệu nó có được yêu cầu hay không . Toàn bộ điểm với động là nó không phải là công việc của trình biên dịch.
Marc Gravell

9

Biểu thức "động" sẽ bị ràng buộc trong thời gian chạy, vì vậy nếu bạn xác định một phương thức tĩnh với chữ ký chính xác hoặc một phương thức thể hiện, trình biên dịch sẽ không kiểm tra nó.

Phương thức "đúng" sẽ được xác định trong thời gian chạy. Trình biên dịch không thể biết nếu có một phương thức hợp lệ ở đó trong thời gian chạy.

Từ khóa "động" được xác định cho ngôn ngữ động và ngôn ngữ tập lệnh, trong đó Phương thức có thể được xác định bất kỳ lúc nào, ngay cả trong thời gian chạy. Những thứ điên rồ

Đây là một mẫu xử lý int nhưng không có chuỗi, vì phương thức nằm trên phiên bản.

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++;
    }
}

Bạn có thể thêm một phương thức để xử lý tất cả các cuộc gọi "sai", không thể xử lý được

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++;
    }
}

Mã gọi trong ví dụ của bạn không nên là Example.Bar (...) thay vì Example.Foo (...)? Foo () không liên quan trong ví dụ của bạn phải không? Tôi không thực sự hiểu ví dụ của bạn. Tại sao thêm phương thức chung chung tĩnh lại gây ra sự cố? Bạn có thể chỉnh sửa câu trả lời của mình để bao gồm phương pháp đó thay vì đưa ra dưới dạng một tùy chọn không?
Mike Scott

nhưng ví dụ tôi đã đăng chỉ có một phương thức thể hiện duy nhất và không có quá tải, vì vậy tại thời điểm biên dịch, bạn biết rằng không có phương thức tĩnh nào có thể được giải quyết. Chỉ khi bạn thêm ít nhất một mã thì tình hình mới thay đổi và mã hợp lệ.
Mike Scott

Nhưng ví dụ này vẫn có nhiều hơn một phương thức Bar (). Ví dụ của tôi chỉ có một phương pháp. Vì vậy, không có khả năng gọi bất kỳ phương thức Bar () tĩnh nào. Cuộc gọi có thể được giải quyết tại thời điểm biên dịch.
Mike Scott

@Mike có thể là! = Là; với tính năng động, không bắt buộc phải làm như vậy
Marc Gravell

@MarcGravell, vui lòng làm rõ?
Mike Scott
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.