Phương thức không tinh khiết được gọi cho trường chỉ đọc


83

Tôi đang sử dụng Visual Studio 2010 + Resharper và nó hiển thị cảnh báo trên mã sau:

if (rect.Contains(point))
{
    ...
}

rectlà một readonly Rectangletrường và Resharper hiển thị cho tôi cảnh báo này:

"Phương thức không tinh khiết được gọi cho trường loại giá trị chỉ đọc."

Các phương pháp không tinh khiết là gì và tại sao cảnh báo này lại hiển thị cho tôi?


Câu trả lời:


96

Trước hết, câu trả lời của Jon, Michael và Jared về cơ bản là đúng nhưng tôi có một số điều nữa tôi muốn bổ sung cho họ.

Phương pháp "không tinh khiết" có nghĩa là gì?

Nó dễ dàng hơn để mô tả các phương pháp thuần túy. Phương pháp "thuần túy" có các đặc điểm sau:

  • Đầu ra của nó hoàn toàn được xác định bởi đầu vào của nó; đầu ra của nó không phụ thuộc vào ngoại cảnh như thời gian trong ngày hoặc các bit trên đĩa cứng của bạn. Sản lượng của nó không phụ thuộc vào lịch sử của nó; gọi phương thức với một đối số cho trước hai lần sẽ cho cùng một kết quả.
  • Một phương pháp thuần túy không tạo ra đột biến có thể quan sát được trong thế giới xung quanh nó. Một phương pháp thuần túy có thể chọn thay đổi trạng thái riêng tư vì lợi ích của hiệu quả, nhưng một phương pháp thuần túy thì không thay đổi một trường đối số của nó.

Ví dụ, Math.Coslà một phương pháp thuần túy. Đầu ra của nó chỉ phụ thuộc vào đầu vào của nó và đầu vào không bị thay đổi bởi lệnh gọi.

Một phương pháp không tinh khiết là một phương pháp không tinh khiết.

Một số mối nguy hiểm khi chuyển cấu trúc chỉ đọc sang các phương thức không tinh khiết là gì?

Có hai điều mà tôi nghĩ đến. Đầu tiên là điều do Jon, Michael và Jared chỉ ra, và đây là điều mà Resharper đang cảnh báo bạn. Khi bạn gọi một phương thức trên một cấu trúc, chúng tôi luôn chuyển một tham chiếu đến biến đó là bộ nhận, trong trường hợp phương thức muốn thay đổi biến.

Vì vậy, điều gì sẽ xảy ra nếu bạn gọi một phương thức như vậy trên một giá trị, thay vì một biến? Trong trường hợp đó, chúng tôi tạo một biến tạm thời, sao chép giá trị vào đó và chuyển một tham chiếu đến biến.

Một biến chỉ đọc được coi là một giá trị, vì nó không thể bị đột biến bên ngoài hàm tạo. Vì vậy, chúng tôi đang sao chép biến sang một biến khác, và phương thức không tinh khiết có thể làm thay đổi bản sao, khi bạn định nó thay đổi biến.

Đó là sự nguy hiểm của việc chuyển một cấu trúc chỉ đọc như một bộ thu . Cũng có nguy cơ truyền một cấu trúc chứa trường chỉ đọc. Một cấu trúc có chứa một trường chỉ đọc là một thực tế phổ biến, nhưng về cơ bản nó là viết séc rằng hệ thống loại không có tiền để chuyển tiền mặt; "chỉ đọc" của một biến cụ thể được xác định bởi chủ sở hữu của bộ nhớ. Một thể hiện của kiểu tham chiếu "sở hữu" bộ nhớ riêng của nó, nhưng một thể hiện của kiểu giá trị thì không!

struct S
{
  private readonly int x;
  public S(int x) { this.x = x; }
  public void Badness(ref S s)
  {
    Console.WriteLine(this.x);   
    s = new S(this.x + 1);
    // This should be the same, right?
    Console.WriteLine(this.x);   
  }
}

Một người cho rằng điều đó this.xsẽ không thay đổi vì x là một trường chỉ đọc và Badnesskhông phải là một phương thức khởi tạo. Nhưng...

S s = new S(1);
s.Badness(ref s);

... chứng tỏ rõ ràng sự giả dối của điều đó. thisstham chiếu đến cùng một biến, và biến đó không chỉ đọc!


Khá đúng đắn, nhưng vui lòng xem xét mã này: struct Id { private readonly int _id; public Id(int id) { _id = id; } public int ToInt() => _id; } Tại sao ToInt không tinh khiết?
boskicthebrain

@boskicthebrain: Câu hỏi của bạn có thực sự là "tại sao Resharper lại coi điều này là không tinh khiết không?" Nếu đó là câu hỏi của bạn thì hãy tìm một người làm việc trên R # và hỏi họ!
Eric Lippert

3
Resharper sẽ đưa ra cảnh báo này ngay cả khi phương thức bị vô hiệu và không làm gì ngoại trừ return. Dựa trên điều đó, tôi đoán rằng tiêu chí duy nhất là phương thức có [Pure]thuộc tính hay không .
Bornfromanegg 14/1217

Tôi thấy tuyên bố này "chúng tôi luôn chuyển một tham chiếu đến biến là người nhận" hơi khó hiểu đối với tôi. Trong trường hợp từ PO, 'biến' đề cập đến điều gì? Tôi sẽ cho rằng đó là rect. Có phải chúng ta đang nói rằng một bản sao của rectđược chuyển cho Containsphương thức không?
xtu

51

Phương thức không tinh khiết là phương thức không được đảm bảo giữ nguyên giá trị như ban đầu.

Trong .NET 4, bạn có thể trang trí các phương thức và kiểu [Pure]để khai báo chúng là thuần túy và R # sẽ lưu ý điều này. Thật không may, bạn không thể áp dụng nó cho các thành viên của người khác và bạn không thể thuyết phục R # rằng một loại / thành viên là thuần túy trong một dự án .NET 3.5 theo như tôi biết. (Điều này khiến tôi đau đầu trong Noda Time mọi lúc.)

Các ý tưởng là nếu bạn đang gọi điện thoại một phương pháp mà đột biến một biến, nhưng bạn gọi nó vào một trường chỉ đọc, nó có thể không làm những gì bạn muốn, vì vậy R # sẽ cảnh báo bạn về việc này. Ví dụ:

public struct Nasty
{
    public int value;

    public void SetValue()
    {
        value = 10;
    }
}

class Test
{
    static readonly Nasty first;
    static Nasty second;

    static void Main()
    {
        first.SetValue();
        second.SetValue();
        Console.WriteLine(first.value);  // 0
        Console.WriteLine(second.value); // 10
    }
}

Đây sẽ là một cảnh báo thực sự hữu ích nếu mọi phương thức thực sự thuần túy được khai báo theo cách đó. Thật không may, chúng không phải vậy, vì vậy có rất nhiều kết quả dương tính giả :(


Vì vậy, điều đó có nghĩa là một phương thức không tinh khiết có thể thay đổi các trường cơ bản của loại giá trị có thể thay đổi được chuyển cho nó?
tính axit

@Acidic: Không phải giá trị đối số - ngay cả một phương thức không tinh khiết cũng có thể làm được điều đó - mà là giá trị bạn gọi nó . (Xem ví dụ của tôi, nơi mà phương pháp này thậm chí không có bất kỳ thông số.)
Jon Skeet

2
Bạn có thể sử dụng JetBrains.Annotations.PureAttributethay vì System.Diagnostics.Contracts.PureAttribute, chúng có cùng ý nghĩa đối với phân tích mã ReSharper và sẽ hoạt động như nhau trên .NET 3.5, .NET 4 hoặc Silverlight. Bạn cũng có thể chú thích bên ngoài các hội đồng mà bạn không sở hữu bằng cách sử dụng các tệp XML (hãy xem thư mục ExternalAnnotations trong đường dẫn bin ReSharper), nó thực sự có thể khá hữu ích!
Julien Lebosquain

5
@JulienLebosquain: Tôi thực sự miễn cưỡng bắt đầu thêm các chú thích dành riêng cho công cụ - đặc biệt là đối với một dự án mã nguồn mở. Thông tin cần biết về như một tùy chọn, nhưng ...
Jon Skeet

1
Trên thực tế, tôi thấy rằng điều đó System.Diagnostics.Contracts.PureAttributekhông ngăn chặn cảnh báo này trong R # 8.2, trong khi JetBrains.Annotations.PureAttributeđó. Hai thuộc tính này cũng có các mô tả khác nhau: PureThuộc tính contract ngụ ý "kết quả chỉ phụ thuộc vào các tham số", trong khi JetBrains Purengụ ý "không gây ra các thay đổi trạng thái có thể nhìn thấy" mà không loại trừ trạng thái đối tượng được sử dụng để tính toán kết quả. (Nhưng vẫn vào hợp đồng Purekhông có tác dụng tương tự trên cảnh báo điều này có lẽ là một lỗi.)
Wormbo

15

Câu trả lời ngắn gọn là đây là dương tính giả, và bạn có thể yên tâm bỏ qua cảnh báo.

Câu trả lời dài hơn là việc truy cập kiểu giá trị chỉ đọc sẽ tạo ra một bản sao của nó, vì vậy mọi thay đổi đối với giá trị được thực hiện bởi một phương thức sẽ chỉ ảnh hưởng đến bản sao. ReSharper không nhận ra rằng đó Containslà một phương pháp thuần túy (có nghĩa là nó không có tác dụng phụ). Eric Lippert nói về nó ở đây: Mutating Readonly Structs


2
Vui lòng không bao giờ bỏ qua cảnh báo này cho đến khi hiểu đầy đủ !!! Một ví dụ điển hình nơi này có thể an toàn bạn là xây dựng này: private readonly SpinLock _spinLock = new SpinLock();- một khóa như vậy sẽ hoàn toàn vô dụng (vì nguyên nhân modifier readonly on-the-fly bản sao được tạo ra mỗi lần Enter phương pháp được gọi vào nó)
Jan

11

Có vẻ như Reshaprer tin rằng phương thức này Containscó thể thay đổi rectgiá trị. Bởi vì rectreadonly structtrình biên dịch C # tạo các bản sao bảo vệ của giá trị để ngăn phương thức thay đổi một readonlytrường. Về cơ bản mã cuối cùng trông như thế này

Rectangle temp = rect;
if (temp.Contains(point)) {
  ...
}

Resharper đang cảnh báo bạn ở đây rằng nó Containscó thể thay đổi recttheo cách có thể bị mất ngay lập tức vì nó xảy ra tạm thời.


Vì vậy, điều đó sẽ không ảnh hưởng đến bất kỳ logic nào được thực hiện trong phương thức, chỉ ngăn nó làm thay đổi giá trị mà nó được gọi, phải không?
tính axit

5

Một phương pháp không tinh khiết là một phương pháp có thể có tác dụng phụ. Trong trường hợp này, Resharper có vẻ nghĩ rằng nó có thể thay đổi rect. Nó có thể không nhưng chuỗi bằng chứng bị phá vỡ.

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.