タプルvs'out 'vs'struct'の2つの値を返します


86

2つの値を返す関数について考えてみます。我々は書ける:

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

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

// Using struct:
MyStruct MyFunction(string input)

どちらがベストプラクティスであり、その理由は何ですか?


文字列は値型ではありません。「2つの値を返す関数を考えてみてください」という意味だったと思います。
Eric Lippert 2011年

@エリック:その通りです。私は不変の型を意味しました。
Xaqron 2011年

そして、クラスの何が問題になっていますか?
Lukasz Madon 2011年

1
@lukas:何もありませんが、確かにベストプラクティスではありません。これは軽量の値(<16 KB)であり、カスタムコードを追加する場合はstructEric前述のように使用します。
Xaqron 2011年

1
TryParseのように、戻りデータを処理する必要があるかどうかを判断するために戻り値が必要な場合にのみ使用します。それ以外の場合は、構造化オブジェクトが値型または参照であるかどうかなど、常に構造化オブジェクトを返す必要があります。タイプは、データをさらに使用するかどうかによって異なります
MikeT 2013年

回答:


93

それぞれに長所と短所があります。

アウトパラメータは高速で安価ですが、変数を渡し、ミューテーションに依存する必要があります。LINQでoutパラメータを正しく使用することはほとんど不可能です。

タプルはガベージコレクションのプレッシャーをかけ、自己文書化されていません。「Item1」はあまり説明的ではありません。

カスタム構造体は、大きい場合はコピーに時間がかかる場合がありますが、自己文書化されており、小さい場合は効率的です。ただし、些細な用途のためにカスタム構造体全体を定義するのも面倒です。

私は、他のすべてのものが等しいカスタム構造体ソリューションに傾倒します。さらに良いのは、1つの値のみを返す関数作成することです。そもそもなぜ2つの値を返すのですか?

更新:この記事の執筆から6年後に出荷されたC#7のタプルは値型であるため、収集圧力が発生する可能性が低いことに注意してください。


2
2つの値を返すことは、多くの場合、オプションタイプまたはADTがない代わりになります。
Anton Tykhyy 2011年

2
他の言語での私の経験から、一般的にタプルはアイテムの迅速で汚いグループ化に使用されると思います。通常は、各アイテムに名前を付けることができるという理由だけで、クラスまたは構造体を作成することをお勧めします。タプルを使用する場合、各値の意味を判別するのが難しい場合があります。ただし、クラス/構造体を作成する時間を節約できます。クラス/構造体が他の場所で使用されない場合は、やりすぎになる可能性があります。
ケビンキャスカート2011年

23
@Xaqron:「タイムアウトのあるデータ」の概念がプログラムで一般的であることがわかった場合は、ジェネリック型「TimeLimited <T>」を作成して、メソッドがTimeLimited <string>またはTimeLimitedを返すようにすることを検討してください。 <ウリ>か何か。TimeLimited <T>クラスには、「残り時間」を通知するヘルパーメソッドを含めることができます。または「期限切れですか?」または何でも。型システムでこのような興味深いセマンティクスをキャプチャしてみてください。
Eric Lippert 2011年

3
絶対に、パブリックインターフェイスの一部としてTupleを使用することは決してありません。しかし、「プライベート」コードの場合でも、タプルを使用する代わりに、適切なタイプから非常に読みやすくなります(特に、自動プロパティを使用してプライベート内部タイプを作成するのがいかに簡単か)。
SolutionYogi

2
収集圧力とはどういう意味ですか?
2016

25

前の回答に加えて、C#7System.Tupleは、参照型とは異なり、値型タプルをもたらし、セマンティクスも改善されています。

名前を付けずに、次の.Item*構文を使用することもできます。

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

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

しかし、この新機能で本当に強力なのは、タプルに名前を付ける機能です。したがって、上記を次のように書き直すことができます。

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

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

破壊もサポートされています:

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


2
これは基本的に、内部のメンバーとして参照を持つ構造体を返すと考えるのは正しいですか?
Austin_Anderson 2017

4
そのパフォーマンスがパラメータの使用と比較してどうであるかを知っていますか?
SpaceMonkey

20

答えは、関数が実行していることのセマンティクスと、2つの値の関係に依存すると思います。

たとえば、TryParseメソッドoutは、解析された値を受け入れるためのパラメーターを受け取りbool、解析が成功したかどうかを示すためにaを返します。2つの値は実際には一緒に属していないため、意味的には意味があり、outパラメーターを使用するとコードの意図が読みやすくなります。

ただし、関数が画面上のオブジェクトのX / Y座標を返す場合、2つの値は意味的に一緒に属しているため、を使用することをお勧めしますstruct

tupleメンバーを取得するための厄介な構文のため、外部コードに表示されるものにはforを使用しないようにします。


セマンティクスの場合は+1。outパラメータを残すことができる場合、あなたの答えはより適切な聖霊降臨祭の参照型nullです。そこにいくつかのnull許容不変型があります。
Xaqron 2011年

3
実際、TryParseの2つの値は、一方を戻り値として、もう一方をByRefパラメーターとして持つことによって暗示されるよりもはるかに、非常に密接に関連しています。多くの点で、返される論理的なものはnull許容型になります。TryParseパターンがうまく機能する場合と、面倒な場合があります( "if"ステートメントで使用できるのは良いことですが、null許容値を返したり、デフォルト値を指定できる場合が多くあります。より便利でしょう)。
スーパーキャット2011年

@supercat私はアンドリューに同意します、彼らは一緒に属していません。それらは関連していますが、リターンは、タンデムで処理する必要のあるものではなく、値をまったく気にする必要があるかどうかを示します。したがって、戻り値を処理した後は、出力値に関連する他の処理には不要になります。これは、キーと値の間に明確で進行中のリンクがあるディクショナリからKeyValuePairを返す場合とは異なります。null許容型が
.Net1.1にあった

@MikeT:マイクロソフトが、構造は単一の値を表すものにのみ使用することを提案したことは非常に残念だと思います。実際、露出フィールド構造は、ダクトテープでバインドされた独立変数のグループを渡すための理想的な媒体です。 。成功インジケーターと値は、後で別々の変数としてより役立つ場合でも、返された時点で一緒意味があります。変数に格納されている公開フィールド構造のフィールドは、それ自体が個別の変数として使用できます。いずれの場合で...
supercat

@MikeT:フレームワーク内で共分散がサポートされている方法とサポートされていない方法のため、try共分散インターフェイスで機能するパターンはT TryGetValue(whatever, out bool success);だけです。そのアプローチは、インターフェースを認めているだろうIReadableMap<in TKey, out TValue> : IReadableMap<out TValue>、とのインスタンスをマッピングしたいコードを聞かせてAnimalのインスタンスにCar受け入れるためにDictionary<Cat, ToyotaCar>[使用しますTryGetValue<TKey>(TKey key, out bool success)TValuerefパラメータとして使用されている場合、そのような差異はあり得ません。
スーパーキャット2013年

2

Outパラメーターを使用するアプローチを使用します。これは、2番目のアプローチでは、Tupleクラスのオブジェクトを作成してから値を追加する必要があるためです。これは、outパラメーターで値を返す場合に比べてコストのかかる操作だと思います。タプルクラスで複数の値を返したい場合(実際には、1つのoutパラメーターを返すだけでは達成できません)、2番目のアプローチに進みます。


同意しoutます。さらにparams、質問をまっすぐに保つために私が言及しなかったキーワードがあります。
Xaqron 2011年

2

構造体の代わりにカスタムクラスを持つというもう1つのオプションについては言及していません。データに関数で操作できるセマンティクスが関連付けられている場合、またはインスタンスサイズが十分に大きい場合(経験則として> 16バイト)、カスタムクラスが優先される場合があります。「out」の使用は、ポインターへの関連付けと参照型の動作の理解が必要なため、パブリックAPIでは推奨されません。

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

タプルは内部使用には適していますが、パブリックAPIでは使用が厄介です。したがって、私の投票は、パブリックAPIの構造体とクラスの間です。


1
値の集計を返すためだけに型が存在する場合は、単純な公開フィールド値型がこれらのセマンティクスに最も明確に適合していると言えます。タイプにフィールド以外に何もない場合、キャプチャされたビューを表すのかライブビューを表すのか(オープンフィールド構造は機能しません)、実行するデータ検証の種類(明らかになし)については疑問の余地はありません。ライブビューとして)など。不変クラスは操作が不便であり、インスタンスを複数回渡すことができる場合にのみパフォーマンス上の利点があります。
スーパーキャット2016

0

「ベストプラクティス」はありません。それはあなたが快適であり、あなたの状況で最もよく機能するものです。これに一貫している限り、投稿したソリューションのいずれにも問題はありません。


3
もちろん、それらはすべて機能します。技術的な利点がない場合でも、専門家が主に使用しているものを知りたいと思っています。
Xaqron 2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.