(này == null) trong C #!


129

Do một lỗi đã được sửa trong C # 4, chương trình sau sẽ được in true. (Dùng thử trong LINQPad)

void Main() { new Derived(); }

class Base {
    public Base(Func<string> valueMaker) { Console.WriteLine(valueMaker()); }
}
class Derived : Base {
    string CheckNull() { return "Am I null? " + (this == null); }
    public Derived() : base(() => CheckNull()) { }
}

Trong VS2008 ở chế độ Phát hành, nó sẽ ném một UnlimitedProgramException. (Ở chế độ Gỡ lỗi, nó hoạt động tốt)

Trong VS2010 Beta 2, nó không biên dịch (Tôi đã không thử Beta 1); Tôi đã học một bài học đắt giá

Có cách nào khác để tạo ra this == nullC # thuần túy không?


3
Đây rất có thể là một lỗi trong trình biên dịch C # 3.0. Nó hoạt động theo cách nó nên trong C # 4.0.
Mehrdad Afshari

82
@SLaks: Vấn đề với các lỗi là bạn có thể mong đợi chúng được sửa chữa tại một số điểm vì vậy việc tìm kiếm chúng "hữu ích" có lẽ là không khôn ngoan.
AnthonyWJones

6
cảm ơn! không biết về LINQPad. nó tuyệt thật!
gai

8
Theo cách nào, chính xác, điều này là hữu ích?
Allen Rice

6
lỗi này hữu ích như thế nào?
BlackTigerX

Câu trả lời:


73

Quan sát này đã được đăng trên StackOverflow trong một câu hỏi khác vào đầu ngày hôm nay.

Câu trả lời tuyệt vời của Marc cho câu hỏi đó chỉ ra rằng theo thông số kỹ thuật (phần 7.5.7), bạn không thể truy cập thisvào ngữ cảnh đó và khả năng thực hiện trong trình biên dịch C # 3.0 là một lỗi. Trình biên dịch C # 4.0 đang hoạt động chính xác theo thông số kỹ thuật (ngay cả trong bản Beta 1, đây là lỗi thời gian biên dịch):

§ 7.5.7 Quyền truy cập này

Một truy cập này bao gồm các từ dành riêng this.

truy cập này:

this

Một này truy cập chỉ được phép trong khối của một constructor dụ, một phương pháp dụ, hoặc một accessor dụ.


2
Tôi không thấy, tại sao trong mã được trình bày trong câu hỏi này, việc sử dụng từ khóa "này" là không hợp lệ. Phương thức CheckNull là một phương thức cá thể bình thường, không có tính chất . Sử dụng "cái này" là hợp lệ 100% trong phương pháp đó và thậm chí so sánh nó với null là hợp lệ. Lỗi nằm ở dòng init cơ sở: đó là cố gắng truyền đại biểu giới hạn thể hiện làm tham số cho ctor cơ sở. Đây là lỗi (một lỗ hổng trong kiểm tra ngữ nghĩa) trong trình biên dịch: KHÔNG nên có thể. Bạn không được phép viết : base(CheckNull())nếu CheckNull không tĩnh và giống như bạn không thể đặt nội tuyến một lambda ràng buộc thể hiện.
quetzalcoatl

4
@quetzalcoatl: thistrong CheckNullphương pháp là hợp pháp. Điều không hợp pháp là ẩn này - về () => CheckNull()cơ bản () => this.CheckNull(), quyền truy cập đang chạy bên ngoài khối của một hàm tạo. Tôi đồng ý rằng phần của thông số tôi trích dẫn chủ yếu tập trung vào tính hợp pháp cú pháp của thistừ khóa, và có lẽ một phần khác giải quyết vấn đề này chính xác hơn, nhưng cũng dễ dàng ngoại suy từ phần này của thông số kỹ thuật.
Mehrdad Afshari

2
Xin lỗi, tôi không đồng ý. Trong khi tôi biết điều đó (và được viết trong phần bình luận ở trên) và bạn cũng biết rằng - bạn chưa đề cập đến nguyên nhân thực sự của vấn đề trong câu trả lời (được chấp nhận) của bạn. Câu trả lời được chấp nhận - vì vậy dường như tác giả cũng đã xem xét nó. Nhưng tôi nghi ngờ rằng tất cả các độc giả sẽ sáng dạ và thông thạo lambdas để nhận ra một lambda-dụ so với static-lambda ngay từ cái nhìn đầu tiên và ánh xạ đến 'cái này' và các vấn đề với IL phát ra :) Đây là lý do tôi thêm ba xu của mình. Bên cạnh đó, tôi đồng ý với mọi thứ khác những gì được tìm thấy, phân tích và mô tả bởi bạn và những người khác :)
quetzalcoatl

24

Việc dịch ngược thô (Reflector không có tối ưu hóa) của nhị phân chế độ Debug là:

private class Derived : Program.Base
{
    // Methods
    public Derived()
    {
        base..ctor(new Func<string>(Program.Derived.<.ctor>b__0));
        return;
    }

    [CompilerGenerated]
    private static string <.ctor>b__0()
    {
        string CS$1$0000;
        CS$1$0000 = CS$1$0000.CheckNull();
    Label_0009:
        return CS$1$0000;
    }

    private string CheckNull()
    {
        string CS$1$0000;
        CS$1$0000 = "Am I null? " + ((bool) (this == null));
    Label_0017:
        return CS$1$0000;
    }
}

Phương thức CompilerGenerated không có ý nghĩa; nếu bạn nhìn vào IL (bên dưới), nó sẽ gọi phương thức trên chuỗi null (!).

   .locals init (
        [0] string CS$1$0000)
    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: stloc.0 
    L_0007: br.s L_0009
    L_0009: ldloc.0 
    L_000a: ret 

Trong chế độ Phát hành, biến cục bộ được tối ưu hóa đi, do đó, nó cố gắng đẩy một biến không tồn tại vào ngăn xếp.

    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: ret 

(Phản xạ gặp sự cố khi biến nó thành C #)


EDIT : Có ai (Eric Lippert?) Biết tại sao trình biên dịch phát ra ldlockhông?


11

Tôi đã có điều đó! (và cũng có bằng chứng)

văn bản thay thế


2
Đã trễ, là một dấu hiệu tôi nên ngừng mã hóa :) Đã hack chúng tôi với công cụ DLR IIRC.
leppie

tạo trình hiển thị trình gỡ lỗi (DebuggerDisplay) cho bất kỳ 'cái này' là gì, và làm cho điều đó đánh lừa bạn là vô giá trị? : D chỉ cần nói '

10

Đây không phải là một "lỗi". Đây là bạn lạm dụng hệ thống loại. Bạn không bao giờ phải chuyển một tham chiếu đến cá thể hiện tại ( this) cho bất kỳ ai trong hàm tạo.

Tôi có thể tạo ra một "lỗi" tương tự bằng cách gọi một phương thức ảo trong hàm tạo của lớp cơ sở.

Chỉ vì bạn có thể làm điều gì đó xấu không có nghĩa là lỗi khi bạn nhận được nó.


14
Đây là một lỗi biên dịch. Nó tạo ra IL không hợp lệ. (Đọc câu trả lời của tôi)
SLaks

Bối cảnh là tĩnh, vì vậy bạn không được phép tham chiếu phương thức cá thể ở giai đoạn đó.
leppie

10
@ Will: Đó là một lỗi trình biên dịch. Trình biên dịch được cho là tạo mã hợp lệ , có thể kiểm chứng được cho đoạn mã đó hoặc nhổ một thông báo lỗi. Khi một trình biên dịch không hoạt động theo thông số kỹ thuật, đó là lỗi .
Mehrdad Afshari

2
@ Will # 4: Khi tôi viết mã, tôi đã không nghĩ về những hàm ý này. Tôi chỉ nhận ra rằng nó không có ý nghĩa khi nó ngừng biên dịch trong VS2010. -
SLaks

3
Nhân tiện, cuộc gọi phương thức ảo trong constructor là một hoạt động hoàn toàn hợp lệ. Nó chỉ không được khuyến khích. Nó có thể dẫn đến thảm họa logic nhưng không bao giờ InvalidProgramException.
Mehrdad Afshari

3

Tôi có thể sai, nhưng tôi khá chắc chắn nếu đối tượng của bạn nullsẽ không bao giờ trở thành một kịch bản thisáp dụng.

Chẳng hạn, bạn sẽ gọi CheckNullnhư thế nào?

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException

3
Trong một lambda trong đối số constructor. Đọc toàn bộ đoạn mã. (Và hãy thử nếu bạn không tin tôi)
SLaks

Tôi đồng ý mặc dù tôi nhớ rất rõ một vài điều về cách trong C ++, một đối tượng không có tham chiếu trong hàm tạo của nó và tôi tự hỏi liệu kịch bản (this == null) có được sử dụng trong các trường hợp đó để kiểm tra xem một cuộc gọi đến một phương thức có được tạo từ hàm tạo của đối tượng trước khi hiển thị một con trỏ tới "this". Mặc dù, theo như tôi biết trong C #, sẽ không có trường hợp nào "cái này" trở thành vô giá trị, ngay cả trong các phương thức Vứt bỏ hoặc quyết toán.
jpierson

Tôi đoán quan điểm của tôi là chính ý tưởng thisloại trừ lẫn nhau về khả năng là null - loại "Cogito, ergo sum" của lập trình máy tính. Do đó, mong muốn của bạn để sử dụng biểu thức this == nullvà bao giờ nó trở lại thực sự đánh tôi là sai lầm.
Dan Tao

Nói cách khác: Tôi đã đọc mã của bạn; Điều tôi đang nói là tôi nghi ngờ những gì bạn đã cố gắng thực hiện ngay từ đầu.
Dan Tao

Mã này chỉ đơn giản là chứng minh lỗi, và, như bạn đã chỉ ra, hoàn toàn vô dụng. Để xem mã thực sự hữu ích, đọc câu trả lời thứ hai của tôi.
SLaks

-1

Không chắc chắn nếu đây là những gì bạn đang tìm kiếm

    public static T CheckForNull<T>(object primary, T Default)
    {
        try
        {
            if (primary != null && !(primary is DBNull))
                return (T)Convert.ChangeType(primary, typeof(T));
            else if (Default.GetType() == typeof(T))
                return Default;
        }
        catch (Exception e)
        {
            throw new Exception("C:CFN.1 - " + e.Message + "Unexpected object type of " + primary.GetType().ToString() + " instead of " + typeof(T).ToString());
        }
        return default(T);
    }

ví dụ: UserID = CheckForNull (Request.QueryString ["UserID"], 147);


13
Bạn hoàn toàn hiểu sai câu hỏi.
SLaks

1
Tôi đã tìm ra nhiều. Tôi nghĩ dù sao tôi cũng sẽ thử.
Scott và nhóm Dev
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.