Trả về hai giá trị, Tuple so với 'out' và 'struct'


86

Hãy xem xét một hàm trả về hai giá trị. Chúng tôi có thể viết:

// Using out:
string MyFunction(string input, out int count)

// Using Tuple class:
Tuple<string, int> MyFunction(string input)

// Using struct:
MyStruct MyFunction(string input)

Phương pháp nào là tốt nhất và tại sao?


Chuỗi không phải là một kiểu giá trị. Tôi nghĩ bạn muốn nói "hãy xem xét một hàm trả về hai giá trị".
Eric Lippert

@Eric: Bạn nói đúng. Ý tôi là các kiểu bất biến.
Xaqron

và có chuyện gì với một lớp học?
Lukasz Madon

1
@lukas: Không có gì, nhưng chắc chắn nó không phải là phương pháp hay nhất. Đây là một giá trị nhẹ (<16 KB) và nếu tôi định thêm mã tùy chỉnh, tôi sẽ tiếp tục structnhư Ericđã đề cập.
Xaqron

1
Tôi sẽ nói rằng chỉ sử dụng hết khi bạn cần giá trị trả về để quyết định xem bạn có nên xử lý dữ liệu trả về hay không, như trong TryParse, nếu không, bạn phải luôn trả về một đối tượng có cấu trúc, như đối tượng có cấu trúc nên là kiểu giá trị hay một tham chiếu loại phụ thuộc vào những gì thêm sử dụng bạn thực hiện của dữ liệu
MikeT

Câu trả lời:


93

Mỗi người đều có ưu và khuyết điểm.

Tham số ra nhanh và rẻ nhưng yêu cầu bạn phải chuyển vào một biến và dựa vào sự đột biến. Hầu như không thể sử dụng chính xác tham số out với LINQ.

Tuples làm cho việc thu gom rác trở nên áp lực và không tự lập tài liệu. "Item1" không phải là rất mô tả.

Các cấu trúc tùy chỉnh có thể sao chép chậm nếu chúng lớn, nhưng có thể tự lập tài liệu và hiệu quả nếu chúng nhỏ. Tuy nhiên, nó cũng là một khó khăn khi xác định một loạt các cấu trúc tùy chỉnh cho các mục đích sử dụng nhỏ.

Tôi sẽ nghiêng về giải pháp cấu trúc tùy chỉnh, tất cả những thứ khác đều bình đẳng. Thậm chí tốt hơn là tạo một hàm chỉ trả về một giá trị . Tại sao bạn trả về hai giá trị ở vị trí đầu tiên?

CẬP NHẬT: Lưu ý rằng các bộ giá trị trong C # 7, được xuất xưởng sáu năm sau khi bài báo này được viết, là các loại giá trị và do đó ít có khả năng tạo ra áp lực thu thập.


2
Trả về hai giá trị thường thay thế cho việc không có loại tùy chọn hoặc ADT.
Anton Tykhyy

2
Từ kinh nghiệm của tôi với các ngôn ngữ khác, tôi có thể nói rằng nói chung các bộ giá trị được sử dụng để phân nhóm các mục nhanh và bẩn. Thông thường tốt hơn là tạo một lớp hoặc cấu trúc đơn giản vì nó cho phép bạn đặt tên cho từng mục. Khi sử dụng các bộ giá trị, ý nghĩa của mỗi giá trị có thể khó xác định. Nhưng nó giúp bạn không mất thời gian để tạo lớp / cấu trúc có thể là quá mức cần thiết nếu lớp / cấu trúc đã nói sẽ không được sử dụng ở nơi khác.
Kevin Cathcart

23
@Xaqron: Nếu bạn thấy rằng ý tưởng "dữ liệu có thời gian chờ" là phổ biến trong chương trình của mình thì bạn có thể cân nhắc tạo một loại chung "TimeLimited <T>" để bạn có thể đặt phương thức của mình trả về TimeLimited <string> hoặc TimeLimited <Uri> hoặc bất cứ điều gì. Sau đó, lớp TimeLimited <T> có thể có các phương thức trợ giúp cho bạn biết "chúng ta còn bao lâu nữa?" hoặc "nó đã hết hạn?" hay bất cứ cái gì. Cố gắng nắm bắt những ngữ nghĩa thú vị như thế này trong hệ thống loại.
Eric Lippert

3
Tuyệt đối, tôi sẽ không bao giờ sử dụng Tuple như một phần của giao diện công khai. Nhưng ngay cả đối với mã 'riêng tư', tôi nhận được khả năng đọc rất lớn từ một loại thích hợp thay vì sử dụng Tuple (đặc biệt là việc tạo một loại riêng bên trong dễ dàng như thế nào với Thuộc tính Tự động).
Giải

2
Áp suất thu có nghĩa là gì?
cuộn

25

Thêm vào các câu trả lời trước, C # 7 mang lại các bộ giá trị kiểu giá trị, không giống như System.Tuplekiểu tham chiếu và cũng cung cấp ngữ nghĩa được cải thiện.

Bạn vẫn có thể không đặt tên cho chúng và sử dụng .Item*cú pháp:

(string, string, int) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.Item1; //John
person.Item2; //Doe
person.Item3;   //42

Nhưng điều thực sự mạnh mẽ về tính năng mới này là khả năng có các bộ giá trị được đặt tên. Vì vậy, chúng tôi có thể viết lại ở trên như thế này:

(string FirstName, string LastName, int Age) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.FirstName; //John
person.LastName; //Doe
person.Age;   //42

Cơ cấu hủy cũng được hỗ trợ:

(string firstName, string lastName, int age) = getPerson()


2
Tôi có đúng khi nghĩ rằng điều này về cơ bản trả về một cấu trúc với các tham chiếu là các thành viên dưới mui xe?
Austin_Anderson

4
chúng ta có biết hiệu suất của điều đó so với việc sử dụng các tham số không?
SpaceMonkey

20

Tôi nghĩ câu trả lời phụ thuộc vào ngữ nghĩa của chức năng đang làm gì và mối quan hệ giữa hai giá trị.

Ví dụ: các TryParsephương thức nhận một outtham số để chấp nhận giá trị được phân tích cú pháp và trả về a boolđể cho biết liệu quá trình phân tích cú pháp có thành công hay không. Hai giá trị không thực sự thuộc về nhau, vì vậy, về mặt ngữ nghĩa, nó có ý nghĩa hơn và mục đích của mã dễ đọc hơn, để sử dụngout tham số.

Tuy nhiên, nếu hàm của bạn trả về tọa độ X / Y của một số đối tượng trên màn hình, thì hai giá trị thuộc về nhau về mặt ngữ nghĩa và tốt hơn là sử dụng struct .

Cá nhân tôi tránh sử dụng một tuplecho bất kỳ thứ gì sẽ hiển thị với mã bên ngoài do cú pháp khó hiểu để truy xuất các thành viên.


+1 cho ngữ nghĩa. Câu trả lời của bạn là các loại tham chiếu phù hợp hơn khi chúng tôi có thể để lại outtham số null. Có một vài kiểu không thay đổi nullable ngoài kia.
Xaqron

3
Trên thực tế, hai giá trị trong TryParse rất thuộc về nhau, nhiều hơn là ngụ ý bằng cách có một giá trị trả về và một giá trị còn lại là tham số ByRef. Theo nhiều cách, điều hợp lý để trả về sẽ là kiểu nullable. Có một số trường hợp mẫu TryParse hoạt động tốt và một số trường hợp gây khó khăn (thật tuyệt khi nó có thể được sử dụng trong câu lệnh "if", nhưng có nhiều trường hợp trả về giá trị nullable hoặc có thể chỉ định giá trị mặc định sẽ thuận tiện hơn).
supercat

@supercat Tôi sẽ đồng ý với andrew, chúng không thuộc về nhau. mặc dù chúng có liên quan đến nhau, nhưng kết quả trả về cho bạn biết liệu bạn có cần bận tâm đến giá trị không phải là thứ cần được xử lý song song. vì vậy, sau khi bạn xử lý, việc trả về không còn cần thiết cho bất kỳ xử lý nào khác liên quan đến giá trị out, điều này khác với việc trả về KeyValuePair từ một từ điển nơi có một liên kết rõ ràng và liên tục giữa khóa và giá trị. mặc dù tôi đồng ý nếu loại nullable đã ở trong Net 1.1 họ có lẽ sẽ sử dụng chúng như là null sẽ là một cách thích hợp để lá cờ không có giá trị
MikeT

@MikeT: Tôi cho rằng điều đặc biệt đáng tiếc là Microsoft đã gợi ý rằng cấu trúc chỉ nên được sử dụng cho những thứ đại diện cho một giá trị duy nhất, trong khi trên thực tế, cấu trúc trường tiếp xúc là phương tiện lý tưởng để truyền cùng nhau một nhóm các biến độc lập được gắn với nhau bằng băng keo . Chỉ báo thành công và giá trị có ý nghĩa cùng nhau tại thời điểm chúng được trả về , ngay cả khi sau đó chúng sẽ hữu ích hơn dưới dạng các biến riêng biệt. Các trường của cấu trúc trường tiếp xúc được lưu trữ trong một biến có thể được sử dụng như các biến riêng biệt. Trong mọi trường hợp ...
supercat

@MikeT: Do các cách hiệp phương sai được và không được hỗ trợ trong khuôn khổ, nên trymẫu duy nhất hoạt động với các giao diện hiệp phương sai là T TryGetValue(whatever, out bool success); cách tiếp cận đó sẽ cho phép các giao diện IReadableMap<in TKey, out TValue> : IReadableMap<out TValue>và cho phép mã muốn ánh xạ các trường hợp của Animalđến các trường hợp Carchấp nhận Dictionary<Cat, ToyotaCar>[using TryGetValue<TKey>(TKey key, out bool success). Không thể có phương sai như vậy nếu TValueđược sử dụng làm reftham số.
supercat

2

Tôi sẽ đi với cách tiếp cận sử dụng tham số Out vì trong cách tiếp cận thứ hai, bạn sẽ yêu cầu tạo và đối tượng của lớp Tuple và sau đó thêm giá trị vào nó, tôi nghĩ là một hoạt động tốn kém so với việc trả về giá trị trong tham số. Mặc dù nếu bạn muốn trả về nhiều giá trị trong Tuple Class (không thể thực hiện được infact chỉ bằng cách trả về một tham số out) thì tôi sẽ chuyển sang cách tiếp cận thứ hai.


Tôi đồng ý với out. Ngoài ra, có một paramstừ khóa tôi không đề cập để giữ câu hỏi thẳng thắn.
Xaqron

2

Bạn đã không đề cập đến một tùy chọn nữa, đó là có một lớp tùy chỉnh thay vì cấu trúc. Nếu dữ liệu có ngữ nghĩa liên quan đến nó mà có thể được vận hành bởi các hàm hoặc kích thước phiên bản đủ lớn (> 16 byte theo quy tắc chung), thì một lớp tùy chỉnh có thể được ưu tiên hơn. Việc sử dụng "out" không được khuyến khích trong API công khai vì liên kết của nó với các con trỏ và yêu cầu hiểu về cách hoạt động của các loại tham chiếu.

https://msdn.microsoft.com/en-us/library/ms182131.aspx

Tuple tốt để sử dụng nội bộ, nhưng việc sử dụng nó rất khó khăn trong API công khai. Vì vậy, phiếu bầu của tôi là giữa cấu trúc và lớp cho API công khai.


1
Nếu một kiểu tồn tại hoàn toàn với mục đích trả về tổng hợp các giá trị, tôi muốn nói rằng kiểu giá trị trường tiếp xúc đơn giản là phù hợp rõ ràng nhất cho những ngữ nghĩa đó. Nếu không có gì trong loại ngoài các trường của nó, sẽ không có bất kỳ câu hỏi nào về loại xác thực dữ liệu mà nó thực hiện (rõ ràng là không có), cho dù nó đại diện cho chế độ xem được chụp hay đang trực tiếp (cấu trúc trường mở không thể hoạt động dưới dạng chế độ xem trực tiếp), v.v ... Các lớp bất biến ít thuận tiện hơn khi làm việc và chỉ mang lại lợi ích về hiệu suất nếu các phiên bản có thể được truyền nhiều lần.
supercat,

0

Không có "thực hành tốt nhất". Đó là những gì bạn cảm thấy thoải mái và những gì hiệu quả nhất trong hoàn cảnh của bạn. Miễn là bạn nhất quán với điều này, không có vấn đề gì với bất kỳ giải pháp nào bạn đã đăng.


3
Tất nhiên chúng đều hoạt động. Trong trường hợp không có lợi thế về kỹ thuật, tôi vẫn tò mò muốn biết những gì chủ yếu được sử dụng bởi các chuyên gia.
Xaqron
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.