Tại sao điều kiện này (null ||! TryParse) lại dẫn đến “sử dụng biến cục bộ chưa được gán”?


98

Đoạn mã sau dẫn đến việc sử dụng biến cục bộ chưa được gán "numberOfGroups" :

int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

Tuy nhiên, mã này hoạt động tốt (mặc dù, ReSharper nói rằng mã = 10này là thừa):

int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

Tôi có thiếu một cái gì đó, hoặc là trình biên dịch không thích của tôi ||?

Tôi đã thu hẹp điều này để dynamicgây ra sự cố ( optionslà một biến động trong đoạn mã trên của tôi). Câu hỏi vẫn còn đó, tại sao tôi không thể làm điều này ?

này không biên dịch:

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        dynamic myString = args[0];

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

Tuy nhiên, mã này thực hiện :

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        var myString = args[0]; // var would be string

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

Tôi không nhận ra dynamicsẽ là một yếu tố trong việc này.


Đừng nghĩ nó đủ thông minh để biết rằng bạn đang không sử dụng các giá trị thông qua vào bạn outparam như là đầu vào
Charleh

3
Mã được đưa ra ở đây không thể hiện hành vi được mô tả; nó hoạt động tốt. Vui lòng đăng mã thực sự thể hiện hành vi bạn đang mô tả mà chúng tôi có thể tự biên dịch. Cung cấp cho chúng tôi toàn bộ hồ sơ.
Eric Lippert

8
Ah, bây giờ chúng ta có một cái gì đó thú vị!
Eric Lippert

1
Không quá ngạc nhiên khi trình biên dịch bối rối vì điều này. Mã trình trợ giúp cho trang web cuộc gọi động có thể có một số luồng điều khiển không đảm bảo việc gán cho outtham số. Chắc chắn rất thú vị khi xem xét trình biên dịch nên tạo ra mã trợ giúp nào để tránh sự cố hoặc nếu điều đó thậm chí có thể xảy ra.
CodesInChaos

1
Thoạt nhìn, điều này chắc chắn trông giống như một con bọ.
Eric Lippert

Câu trả lời:


73

Tôi khá chắc chắn đây là một lỗi của trình biên dịch. Rất vui!

Chỉnh sửa: nó không phải là một lỗi, như Quartermeister chứng minh; động có thể triển khai một truetoán tử lạ có thể ykhông bao giờ được khởi tạo.

Đây là một repro tối thiểu:

class Program
{
    static bool M(out int x) 
    { 
        x = 123; 
        return true; 
    }
    static int N(dynamic d)
    {
        int y;
        if(d || M(out y))
            y = 10;
        return y; 
    }
}

Tôi không hiểu tại sao điều đó lại là bất hợp pháp; nếu bạn thay thế dynamic bằng bool, nó sẽ biên dịch tốt.

Tôi thực sự sẽ họp với nhóm C # vào ngày mai; Tôi sẽ đề cập nó với họ. Xin lỗi vì lỗi!


6
Tôi rất vui khi biết rằng tôi sẽ không phát điên :) Tôi đã cập nhật mã của mình để chỉ dựa vào TryParse, vì vậy tôi đã sẵn sàng cho bây giờ. Cảm ơn cho cái nhìn sâu sắc của bạn!
Brandon Martinez

4
@NominSim: Giả sử phân tích thời gian chạy không thành công: sau đó một ngoại lệ được đưa ra trước khi địa chỉ cục bộ được đọc. Giả sử phân tích thời gian chạy thành công: sau đó tại thời gian chạy, d là đúng và y được đặt, hoặc d là sai và M đặt y. Dù bằng cách nào, y đã được đặt. Thực tế là phân tích được trì hoãn cho đến khi thời gian chạy không thay đổi bất cứ điều gì.
Eric Lippert

2
Trong trường hợp bất kỳ ai tò mò: Tôi vừa kiểm tra, và trình biên dịch Mono đã làm đúng. imgur.com/g47oquT
Dan Tao

17
Tôi nghĩ rằng hành vi của trình biên dịch thực sự đúng, vì giá trị của dcó thể thuộc loại có truetoán tử được nạp chồng . Tôi đã đăng một câu trả lời với một ví dụ mà không có chi nhánh nào được sử dụng.
Quartermeister

2
@Quartermeister trong trường hợp trình biên dịch Mono đang làm sai :)
porges

52

Biến có thể không được gán nếu giá trị của biểu thức động thuộc loại có toán tử được nạp chồngtrue .

Các ||nhà điều hành sẽ gọi các truenhà điều hành để quyết định xem có nên đánh giá phía bên tay phải, và sau đó iftuyên bố sẽ gọi các truenhà điều hành để quyết định có để đánh giá cơ thể của nó. Đối với thông thường bool, chúng sẽ luôn trả về cùng một kết quả và vì vậy chính xác một sẽ được đánh giá, nhưng đối với toán tử do người dùng định nghĩa thì không có gì đảm bảo như vậy!

Dựa trên repro của Eric Lippert, đây là một chương trình ngắn và đầy đủ thể hiện trường hợp không có đường dẫn nào sẽ được thực thi và biến sẽ có giá trị ban đầu:

using System;

class Program
{
    static bool M(out int x)
    {
        x = 123;
        return true;
    }

    static int N(dynamic d)
    {
        int y = 3;
        if (d || M(out y))
            y = 10;
        return y;
    }

    static void Main(string[] args)
    {
        var result = N(new EvilBool());
        // Prints 3!
        Console.WriteLine(result);
    }
}

class EvilBool
{
    private bool value;

    public static bool operator true(EvilBool b)
    {
        // Return true the first time this is called
        // and false the second time
        b.value = !b.value;
        return b.value;
    }

    public static bool operator false(EvilBool b)
    {
        throw new NotImplementedException();
    }
}

8
Làm tốt lắm. Tôi đã chuyển nó cho nhóm thiết kế và kiểm tra C #; Tôi sẽ xem nếu họ có bất kỳ nhận xét nào về nó khi tôi gặp họ vào ngày mai.
Eric Lippert

3
Điều này rất lạ đối với tôi. Tại sao phải dđánh giá hai lần? (Tôi không tranh cãi rằng nó rõ ràng , như bạn đã trình bày.) Tôi đã mong đợi kết quả được đánh giá của true(từ lệnh gọi toán tử đầu tiên, nguyên nhân bởi ||) được "chuyển đến" ifcâu lệnh. Đó chắc chắn là điều sẽ xảy ra nếu bạn đặt một lệnh gọi hàm vào đó chẳng hạn.
Dan Tao

3
@DanTao: Biểu thức chỉ dđược đánh giá một lần, như bạn mong đợi. Đó là truetoán tử được gọi hai lần, một lần ||và một lần if.
Quartermeister

2
@DanTao: Có thể rõ ràng hơn nếu chúng ta đưa chúng vào các câu lệnh riêng biệt như var cond = d || M(out y); if (cond) { ... }. Đầu tiên, chúng tôi đánh giá dđể có được một EvilBooltham chiếu đối tượng. Để đánh giá ||, trước tiên chúng tôi gọi EvilBool.truevới tham chiếu đó. Điều đó trả về true, vì vậy chúng tôi ngắn mạch và không gọi M, sau đó gán tham chiếu cho cond. Sau đó, chúng ta chuyển sang ifcâu lệnh. Câu iflệnh đánh giá điều kiện của nó bằng cách gọi EvilBool.true.
Quartermeister

2
Bây giờ điều này thực sự tuyệt vời. Tôi không biết có toán tử đúng hay sai.
IllidanS4 muốn Monica trở lại

7

Từ MSDN (tôi nhấn mạnh):

Kiểu động cho phép các hoạt động mà nó xảy ra để bỏ qua kiểm tra kiểu thời gian biên dịch . Thay vào đó, các hoạt động này được giải quyết tại thời điểm chạy . Loại động đơn giản hóa quyền truy cập vào các API COM như API tự động hóa Office và cũng như các API động như thư viện IronPython và vào Mô hình đối tượng tài liệu HTML (DOM).

Kiểu động hoạt động giống như đối tượng kiểu trong hầu hết các trường hợp. Tuy nhiên, các hoạt động có chứa biểu thức kiểu động không được giải quyết hoặc kiểu được trình biên dịch kiểm tra.

Vì trình biên dịch không gõ kiểm tra hoặc giải quyết bất kỳ hoạt động nào có chứa biểu thức kiểu động, nên nó không thể đảm bảo rằng biến sẽ được gán thông qua việc sử dụng TryParse().


Nếu điều kiện đầu tiên được đáp ứng, numberGroupssẽ được gán (trong if truekhối), nếu không, điều kiện thứ hai đảm bảo gán (thông qua out).
leppie,

1
Đó là một suy nghĩ thú vị, nhưng mã biên dịch tốt mà không có myString == null(chỉ dựa vào TryParse).
Brandon Martinez

1
@leppie Vấn đề là vì điều kiện đầu tiên (thực sự là do toàn bộ ifbiểu thức) liên quan đến một dynamicbiến, nó không được giải quyết tại thời điểm biên dịch (do đó trình biên dịch không thể đưa ra những giả định đó).
NominSim

@NominSim: Tôi hiểu ý bạn :) +1 Có thể là một sự hy sinh từ trình biên dịch (vi phạm các quy tắc C #), nhưng các đề xuất khác dường như ngụ ý một lỗi. Đoạn trích của Eric cho thấy đây không phải là một sự hy sinh, mà là một lỗi.
leppie

@NominSim Điều này không thể đúng; chỉ vì một số chức năng biên dịch nhất định bị hoãn không có nghĩa là tất cả chúng đều như vậy. Có rất nhiều bằng chứng cho thấy rằng trong các trường hợp hơi khác nhau, trình biên dịch thực hiện phân tích nhiệm vụ xác định mà không gặp vấn đề gì, mặc dù có sự hiện diện của một biểu thức động.
dlev
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.