Tham số có thể không đổi?


83

Tôi đang tìm kiếm C # tương đương với Java final. Nó tồn tại?

C # có bất cứ điều gì như sau:

public Foo(final int bar);

Trong ví dụ trên, barlà một biến chỉ đọc và không thể thay đổi bởi Foo(). Có cách nào để làm điều này trong C # không?

Ví dụ, có lẽ tôi có một phương pháp lâu rằng sẽ làm việc với x, yztọa độ của một số đối tượng (kiểu int). Tôi muốn hoàn toàn chắc chắn rằng hàm không thay đổi các giá trị này theo bất kỳ cách nào, do đó làm hỏng dữ liệu. Vì vậy, tôi muốn tuyên bố chúng chỉ đọc.

public Foo(int x, int y, int z) {
     // do stuff
     x++; // oops. This corrupts the data. Can this be caught at compile time?
     // do more stuff, assuming x is still the original value.
}

Bản sao của stackoverflow.com/questions/2125591/… (và có câu trả lời tuyệt vời của Eric Lippert, btw)
casperMột

12
Tôi không nghĩ nó trùng lặp chính xác. Câu hỏi đó là về sự khác biệt giữa chuyển theo tham chiếu và chuyển theo giá trị. Tôi nghĩ Rosarch có thể đã sử dụng mã ví dụ xấu cho điểm mà anh ta đang cố gắng vượt qua.
Corey Sunwold

@Rosarch: Về những gì tôi hiểu từ từ "cuối cùng", là bạn có thể thực hiện thêm bất kỳ hành động nào với đối tượng, bất kể đó là gì. Tôi hiểu rằng "cuối cùng" được áp dụng cho một lớp sẽ tương đương với "niêm phong" trong C #. Nhưng lợi ích hay cách sử dụng thực sự của từ khóa "cuối cùng" này là gì? Tôi đã thấy nó gần như ở bất kỳ nơi nào vào một ngày nào đó trong mã nguồn JAVA.
Will Marcouiller

3
@Will Marcouiller Với từ khóa cuối cùng, nó không phải là đối tượng không thể thay đổi, bởi vì bạn có thể sử dụng phương thức thay đổi bên trong của đối tượng, tham chiếu của nó đến đối tượng không thể thay đổi. Trong trường hợp chuyển theo giá trị, giống như ví dụ đã đưa ra, nó sẽ ngăn x ++ trở thành một hoạt động hợp lệ vì giá trị sẽ thay đổi. Ưu điểm sẽ là bạn nhận được kiểm tra thời gian biên dịch để đảm bảo rằng không có gì đặt giá trị khác nhau. Tuy nhiên, theo kinh nghiệm của tôi, tôi chưa bao giờ cần một tính năng như vậy.
Corey Sunwold

+1 @Corey Sunwold: Cảm ơn vì sự chính xác này. Hiện tại, tôi chỉ biết sự tương đồng giữa C # và JAVA, biết rằng C # "kế thừa" từ JAVA trong các khái niệm của nó. Điều này nói lên rằng nó khuyến khích tôi tìm hiểu thêm về JAVA vì tôi biết rằng nó đã được phổ biến rộng rãi trên toàn thế giới.
Will Marcouiller

Câu trả lời:


60

Rất tiếc, bạn không thể làm điều này trong C #.

Các consttừ khóa chỉ có thể được sử dụng cho các biến địa phương và các lĩnh vực.

Các readonlytừ khóa chỉ có thể được sử dụng trên đồng ruộng.

LƯU Ý: Ngôn ngữ Java cũng hỗ trợ có các tham số cuối cùng cho một phương thức. Chức năng này không tồn tại trong C #.

từ http://www.25hoursaday.com/CsharpVsJava.html

EDIT (2019/08/13): Tôi đang sử dụng cái này để hiển thị vì nó được chấp nhận và cao nhất trong danh sách. Nó bây giờ là có thể với incác thông số. Xem câu trả lời bên dưới câu trả lời này để biết chi tiết.


1
"Không may"? Điều đó sẽ khác với khả năng hiện tại như thế nào?
John Saunders

31
@John Saunders Thật không may vì Rosarch đang tìm kiếm một tính năng không tồn tại.
Corey Sunwold

7
@John: Nó không phải là hằng số trong phương thức. Đó là vấn đề.
Noon Silk

6
Hằng số duy nhất của nó nằm ngoài phạm vi của phương pháp này. Bỏ qua việc nó được truyền theo tham chiếu hay theo giá trị, Rosarch không muốn tham số thay đổi trong phạm vi của phương thức. Đó là sự khác biệt chính.
Corey Sunwold

5
@silky: đọc bài của anh ấy, đó không phải là những gì anh ấy yêu cầu. Anh ấy đang yêu cầu đảm bảo phương pháp không thay đổi các tham số thực tế.
John Saunders

30

Điều này hiện có thể thực hiện trong C # phiên bản 7.2:

Bạn có thể sử dụng intừ khóa trong chữ ký phương thức. Tài liệu MSDN .

Các intừ khóa nên được bổ sung trước khi xác định lập luận của phương pháp.

Ví dụ, một phương thức hợp lệ trong C # 7.2:

public long Add(in long x, in long y)
{
    return x + y;
}

Mặc dù những điều sau đây không được phép:

public long Add(in long x, in long y)
{
    x = 10; // It is not allowed to modify an in-argument.
    return x + y;
}

Lỗi sau sẽ được hiển thị khi cố gắng sửa đổi một trong hai xhoặc yvì chúng được đánh dấu bằng in:

Không thể gán cho biến 'in long' vì nó là biến chỉ đọc

Đánh dấu một đối số có innghĩa là:

Phương thức này không sửa đổi giá trị của đối số được sử dụng làm tham số này.


3
Tất nhiên, nó không hữu ích cho các loại tham chiếu. Sử dụng intừ khóa cho các tham số với các loại đó, chỉ ngăn chặn việc gán cho chúng. Không ngăn cản việc sửa đổi các trường hoặc thuộc tính có thể truy cập của nó! Tôi nói đúng chứ?
ABS

5
Xin lưu ý rằng nó chỉ hoạt động tốt với cấu trúc / giá trị và KHÔNG hoạt động với các lớp, nơi bạn có thể sửa đổi phiên bản bằng ref để điều đó tồi tệ hơn nhiều. Và bạn không nhận được bất kỳ cảnh báo nào. Sử dụng cẩn thận. blog.dunnhq.com/index.php/2017/12/15/…
jeromej

8

Đây là một câu trả lời ngắn gọn và ngọt ngào có thể sẽ nhận được rất nhiều phiếu bầu. Tôi chưa đọc tất cả các bài đăng và nhận xét, vì vậy xin hãy tha thứ cho tôi nếu điều này đã được đề xuất trước đó.

Tại sao không lấy các tham số của bạn và chuyển chúng vào một đối tượng hiển thị chúng là bất biến và sau đó sử dụng đối tượng đó trong phương thức của bạn?

Tôi nhận ra rằng đây có lẽ là một công việc rất rõ ràng xung quanh đã được xem xét và OP đang cố gắng tránh làm điều này bằng cách đặt câu hỏi này, nhưng tôi cảm thấy nó không nên ở đây ...

Chúc may mắn :-)


1
Vì thành viên vẫn có thể được sửa đổi. C này ++ đang cho thấy: int* p; *p = 0;. Điều đó sẽ biên dịch và chạy cho đến khi lỗi phân đoạn.
Cole Johnson

Tôi đã bỏ phiếu vì nó không phải là giải pháp cho vấn đề. Bạn cũng có thể lưu các đối số ở đầu hàm và so sánh chúng ở cuối, và đưa ra một ngoại lệ khi thay đổi. Tôi sẽ giữ nó trong túi sau của mình nếu có một cuộc thi tìm giải pháp tồi tệ nhất :)
Rick O'Shea

8

Câu trả lời: C # không có chức năng const như C ++.

Tôi đồng ý với Bennett Dill.

Từ khóa const rất hữu ích. Trong ví dụ, bạn đã sử dụng một số nguyên và mọi người không hiểu ý bạn. Nhưng, tại sao nếu tham số của bạn là một đối tượng lớn và phức tạp của người dùng mà không thể thay đổi bên trong hàm đó? Đó là việc sử dụng từ khóa const: tham số không thể thay đổi bên trong phương thức đó vì [bất kỳ lý do gì ở đây] không quan trọng đối với phương thức đó. Từ khóa Const rất mạnh và tôi thực sự nhớ nó trong C #.


7

Tôi sẽ bắt đầu với intphần. intlà một kiểu giá trị và trong .Net, điều đó có nghĩa là bạn thực sự đang xử lý một bản sao. Đó là một ràng buộc thiết kế thực sự kỳ lạ khi nói với một phương pháp "Bạn có thể có một bản sao của giá trị này. Đó là bản sao của bạn, không phải của tôi; Tôi sẽ không bao giờ nhìn thấy nó nữa. Nhưng bạn không thể thay đổi bản sao." Trong lời gọi phương thức, việc sao chép giá trị này là được, nếu không thì chúng ta không thể gọi phương thức một cách an toàn. Nếu phương thức cần bản gốc, hãy để người thực hiện tạo một bản sao để lưu. Cung cấp giá trị cho phương thức hoặc không cung cấp giá trị cho phương thức. Đừng đi tất cả những điều khôn ngoan ở giữa.

Hãy chuyển sang các loại tham chiếu. Bây giờ nó có một chút khó hiểu. Ý của bạn là một tham chiếu không đổi, nơi mà bản thân tham chiếu không thể thay đổi được, hay một đối tượng hoàn toàn bị khóa, không thể thay đổi? Nếu trước đó, các tham chiếu trong .Net theo mặc định được chuyển theo giá trị. Đó là, bạn nhận được một bản sao của tài liệu tham khảo. Vì vậy, về cơ bản chúng ta có cùng một tình huống như đối với các loại giá trị. Nếu người triển khai sẽ cần tham chiếu ban đầu, họ có thể tự giữ nó.

Điều đó chỉ để lại cho chúng ta đối tượng không đổi (bị khóa / không thay đổi). Điều này có vẻ ổn từ góc độ thời gian chạy, nhưng làm cách nào để trình biên dịch thực thi nó? Vì tất cả các thuộc tính và phương pháp đều có thể có tác dụng phụ, về cơ bản bạn sẽ bị giới hạn quyền truy cập trường chỉ đọc. Một đối tượng như vậy có thể không thú vị lắm.


1
Tôi đã phản đối bạn vì hiểu sai câu hỏi; nó không phải là thay đổi giá trị SAU cuộc gọi, mà là thay đổi nó NGAY BÂY GIỜ.
Noon Silk

2
@silky - Tôi không hiểu sai câu hỏi. Tôi cũng đang nói về chức năng này. Tôi đang nói rằng đó là một hạn chế kỳ lạ khi gửi đến một hàm, bởi vì nó không thực sự ngăn chặn bất cứ điều gì. Nếu ai đó quên thông số ban đầu đã thay đổi, họ cũng có khả năng quên một bản sao đã thay đổi.
Joel Coehoorn

1
Tôi không đồng ý. Chỉ cung cấp một bình luận giải thích sự ủng hộ.
Noon Silk

DateTime chỉ có quyền truy cập trường chỉ đọc, nhưng nó vẫn thú vị. Nó có nhiều phương thức trả về các phiên bản mới. Nhiều ngôn ngữ chức năng tránh các phương pháp có tác dụng phụ và thích các kiểu không thay đổi, theo tôi điều này làm cho mã dễ theo dõi hơn.
Devin Garner

DateTime cũng vẫn là một kiểu giá trị, không phải là một kiểu tham chiếu.
Joel Coehoorn

5

Tạo một giao diện cho lớp của bạn mà chỉ có những người truy cập thuộc tính chỉ đọc. Sau đó, hãy để tham số của bạn là của giao diện đó thay vì của chính lớp. Thí dụ:

public interface IExample
{
    int ReadonlyValue { get; }
}

public class Example : IExample
{
    public int Value { get; set; }
    public int ReadonlyValue { get { return this.Value; } }
}


public void Foo(IExample example)
{
    // Now only has access to the get accessors for the properties
}

Đối với cấu trúc, hãy tạo một trình bao bọc const chung.

public struct Const<T>
{
    public T Value { get; private set; }

    public Const(T value)
    {
        this.Value = value;
    }
}

public Foo(Const<float> X, Const<float> Y, Const<float> Z)
{
// Can only read these values
}

Tuy nhiên, điều đáng chú ý là bạn muốn làm những gì bạn yêu cầu làm liên quan đến cấu trúc, với tư cách là tác giả của phương pháp, bạn nên mong đợi biết điều gì đang xảy ra trong phương thức đó. Nó sẽ không ảnh hưởng đến các giá trị được truyền vào để sửa đổi chúng trong phương thức, vì vậy mối quan tâm duy nhất của bạn là đảm bảo rằng bạn tự xử lý theo phương thức bạn đang viết. Có một điểm mà sự cảnh giác và mã sạch là chìa khóa, việc thực thi quá mức const và các quy tắc khác.


Điều đó thực sự khá thông minh. Ngoài ra, tôi muốn lưu ý rằng bạn có thể kế thừa nhiều giao diện cùng một lúc - nhưng bạn chỉ có thể kế thừa một lớp.
Natalie Adams

Điều này rất hữu ích ... và cũng làm giảm bớt sự khó chịu vì nó bị thiếu trong c #. Cảm ơn
Jimmyt1988

3

Nếu bạn thường xuyên gặp rắc rối như thế này thì bạn nên xem xét "ứng dụng hungarian". Loại tốt, đối lập với loại xấu . Mặc dù điều này thường không cố gắng thể hiện hằng số của một tham số phương thức (điều đó quá bất thường), nhưng chắc chắn không có gì ngăn bạn thêm một chữ "c" vào trước tên mã định danh.

Đối với tất cả những người đau đầu để đóng nút downvote ngay bây giờ, vui lòng đọc ý kiến ​​của những người nổi tiếng này về chủ đề:


Lưu ý rằng bạn không cần quy ước đặt tên để thực thi điều này; bạn luôn có thể làm điều đó bằng cách sử dụng một công cụ phân tích sau thực tế (không tuyệt vời, nhưng tuy nhiên). Tôi tin rằng Gendarme ( mono-project.com/Gendarme ) có một quy tắc cho nó và có lẽ cả StyleCop / FxCop nữa.
Noon Silk

Dễ dàng cố gắng tồi tệ nhất (không thành công) ở một giải pháp. Vì vậy, bây giờ tham số của bạn không chỉ có thể ghi được, nó đang thiết lập cho bạn thất bại bằng cách nói dối bạn về việc nó không thay đổi.
Rick O'Shea

Hai người dùng SO đau đớn cho đến nay, có thể còn tồi tệ hơn.
Hans Passant

2

Tôi biết điều này có thể hơi muộn. Nhưng đối với những người vẫn đang tìm kiếm các cách khác cho điều này, có thể có một cách khác để giải quyết hạn chế này của tiêu chuẩn C #. Chúng ta có thể viết lớp wrapper ReadOnly <T> trong đó T: struct. Với chuyển đổi ngầm định sang kiểu cơ sở T. Nhưng chỉ chuyển đổi rõ ràng sang lớp wrapper <T>. Điều này sẽ thực thi các lỗi trình biên dịch nếu nhà phát triển cố gắng đặt ngầm định thành giá trị của loại ReadOnly <T>. Như tôi sẽ trình bày hai cách sử dụng có thể có dưới đây.

SỬ DỤNG 1 định nghĩa người gọi bắt buộc để thay đổi. Việc sử dụng này sẽ chỉ được sử dụng trong việc kiểm tra tính đúng đắn của mã chức năng "TestCalled" của bạn. Trong khi ở cấp độ phát hành / bản dựng, bạn không nên sử dụng nó. Vì trong các phép toán quy mô lớn có thể chuyển đổi quá mức cần thiết và làm cho mã của bạn chậm. Tôi sẽ không sử dụng nó, nhưng chỉ với mục đích trình diễn, tôi đã đăng nó.

SỬ DỤNG 2 mà tôi muốn đề xuất, có sử dụng Gỡ lỗi và Phát hành được chứng minh trong chức năng TestCalled2. Ngoài ra, sẽ không có chuyển đổi trong chức năng TestCaller khi sử dụng cách tiếp cận này, nhưng nó yêu cầu thêm một chút mã hóa các định nghĩa TestCaller2 bằng cách sử dụng điều hòa trình biên dịch. Bạn có thể nhận thấy lỗi trình biên dịch trong cấu hình gỡ lỗi, trong khi cấu hình phát hành, tất cả mã trong hàm TestCalled2 sẽ biên dịch thành công.

using System;
using System.Collections.Generic;

public class ReadOnly<VT>
  where VT : struct
{
  private VT value;
  public ReadOnly(VT value)
  {
    this.value = value;
  }
  public static implicit operator VT(ReadOnly<VT> rvalue)
  {
    return rvalue.value;
  }
  public static explicit operator ReadOnly<VT>(VT rvalue)
  {
    return new ReadOnly<VT>(rvalue);
  }
}

public static class TestFunctionArguments
{
  static void TestCall()
  {
    long a = 0;

    // CALL USAGE 1.
    // explicite cast must exist in call to this function
    // and clearly states it will be readonly inside TestCalled function.
    TestCalled(a);                  // invalid call, we must explicit cast to ReadOnly<T>
    TestCalled((ReadOnly<long>)a);  // explicit cast to ReadOnly<T>

    // CALL USAGE 2.
    // Debug vs Release call has no difference - no compiler errors
    TestCalled2(a);

  }

  // ARG USAGE 1.
  static void TestCalled(ReadOnly<long> a)
  {
    // invalid operations, compiler errors
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }


  // ARG USAGE 2.
#if DEBUG
  static void TestCalled2(long a2_writable)
  {
    ReadOnly<long> a = new ReadOnly<long>(a2_writable);
#else
  static void TestCalled2(long a)
  {
#endif
    // invalid operations
    // compiler will have errors in debug configuration
    // compiler will compile in release
    a = 10L;
    a += 2L;
    a -= 2L;
    a *= 2L;
    a /= 2L;
    a++;
    a--;
    // valid operations
    // compiler will compile in both, debug and release configurations
    long l;
    l = a + 2;
    l = a - 2;
    l = a * 2;
    l = a / 2;
    l = a ^ 2;
    l = a | 2;
    l = a & 2;
    l = a << 2;
    l = a >> 2;
    l = ~a;
  }

}

Sẽ tốt hơn nếu ReadOnly là struct và VT có thể là cả struct và class, theo cách đó nếu VT là struct thì giá trị sẽ được chuyển theo giá trị và nếu đó là lớp thì giá trị sẽ được chuyển bằng tham chiếu và nếu bạn muốn nó giống như java cuối cùng của toán tử ReadOnly phải là ẩn.
Tomer Wolberg

0

Nếu struct được truyền vào một phương thức, trừ khi nó được truyền bởi ref, nó sẽ không bị thay đổi bởi phương thức mà nó được truyền vào. Vì vậy, theo nghĩa đó, có.

Bạn có thể tạo một tham số có giá trị không thể được chỉ định trong phương thức hoặc có thuộc tính không thể được đặt khi ở trong phương thức không? Không. Bạn không thể ngăn giá trị được gán trong phương thức, nhưng bạn có thể ngăn việc thiết lập các thuộc tính của nó bằng cách tạo một kiểu không thay đổi.

Câu hỏi không phải là liệu tham số hay thuộc tính của nó có thể được gán cho trong phương thức hay không. Câu hỏi là nó sẽ như thế nào khi phương thức thoát.

Lần duy nhất bất kỳ dữ liệu bên ngoài nào sẽ bị thay đổi là nếu bạn chuyển một lớp vào và thay đổi một trong các thuộc tính của nó hoặc nếu bạn chuyển một giá trị bằng cách sử dụng từ khóa ref. Tình huống bạn đã nêu ra cũng không.


Tôi sẽ +1 ngoại trừ nhận xét về tính bất biến ... có một loại bất biến sẽ không đạt được những gì anh ấy đang tìm kiếm.
Adam Robinson

Chắc chắn nó sẽ, theo mặc định. Một kiểu không thay đổi, không được truyền bởi tham chiếu, sẽ đảm bảo rằng giá trị, bên ngoài phương thức, không thay đổi.
David Morton

1
Đúng, nhưng ví dụ của anh ấy là về việc thay đổi giá trị bên trong phương thức. Trong mã ví dụ của anh ấy, x là một Int32, là bất biến, nhưng anh ấy vẫn được phép viết x++. Đó là, anh ta đang cố gắng ngăn chặn việc gán lại tham số, tham số này trực giao với khả năng thay đổi của giá trị tham số.
itowlson

1
@silky Kinda kỳ lạ khi phản đối ba câu trả lời gây hiểu lầm cho cùng một câu hỏi. Có lẽ không phải lỗi của người đọc mà câu hỏi đã bị hiểu sai. Bạn biết đấy, khi một người đàn ông ly hôn với người vợ thứ ba, đó thường không phải là lỗi của những người vợ.
David Morton

2
@David: Đó là mục đích của hệ thống bỏ phiếu. Để sắp xếp theo mức độ liên quan. Tôi không hiểu tại sao nó phải lo lắng cho bạn để được sửa chữa.
Noon Silk
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.