두 값 반환, Tuple vs 'out'vs 'struct'


88

두 개의 값을 반환하는 함수를 고려하십시오. 우리는 쓸 수있다:

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

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

// Using struct:
MyStruct MyFunction(string input)

어떤 것이 모범 사례이며 그 이유는 무엇입니까?


문자열이 값 유형이 아닙니다. 나는 당신이 "두 개의 값을 반환하는 함수를 고려하라"는 것을 의미했다고 생각한다.
Eric Lippert 2011-06-17

@Eric : 맞아요. 불변 유형을 의미했습니다.
Xaqron

수업에 무슨 문제가 있습니까?
루카스 마돈

1
@lukas : 아무것도 아니지만 확실히 모범 사례가 아닙니다. 이 경량 값 (<16킬로바이트) 내가 사용자 지정 코드를 추가하는거야, 내가 갈거야 struct으로 Eric언급했다.
Xaqron

1
TryParse에서와 같이 반환 데이터를 처리해야하는지 여부를 결정하기 위해 반환 값이 필요한 경우에만 out을 사용하고 그렇지 않으면 구조화 된 개체가 값 형식 또는 참조 여야하는 것처럼 항상 구조화 된 개체를 반환해야합니다. 유형은 데이터의 할 것을 추가 사용에 따라 달라집니다
MikeT

답변:


94

그들은 각각 장단점이 있습니다.

Out 매개 변수는 빠르고 저렴하지만 변수를 전달하고 변이에 의존해야합니다. LINQ에서 out 매개 변수를 올바르게 사용하는 것은 거의 불가능합니다.

튜플은 가비지 수집에 압력을 가하며 자체 문서화되지 않습니다. "Item1"은 그다지 설명 적이 지 않습니다.

사용자 지정 구조체는 크면 복사 속도가 느릴 수 있지만 자체 문서화되고 작 으면 효율적입니다. 그러나 사소한 용도로 전체 사용자 정의 구조체를 정의하는 것도 고통입니다.

나는 사용자 정의 구조체 솔루션이 다른 모든 것들이 동일하다는 경향이 있습니다. 더 좋은 방법 은 하나의 값만 반환하는 함수만드는 것 입니다. 처음에 두 개의 값을 반환하는 이유는 무엇입니까?

업데이트 :이 기사가 작성된 후 6 년 후에 출시 된 C # 7의 튜플은 값 유형이므로 수집 압력을 유발할 가능성이 적습니다.


2
두 값을 반환하는 것은 종종 옵션 유형 또는 ADT가없는 대신에 사용됩니다.
안톤 Tykhyy

2
다른 언어에 대한 경험으로 볼 때 일반적으로 튜플은 빠르고 더러운 항목 그룹화에 사용됩니다. 일반적으로 각 항목의 이름을 지정할 수 있기 때문에 클래스 또는 구조체를 만드는 것이 좋습니다. 튜플을 사용할 때 각 값의 의미를 결정하기 어려울 수 있습니다. 그러나 클래스 / 구조체가 다른 곳에서 사용되지 않을 경우 과잉 일 수있는 클래스 / 구조체를 만드는 데 시간을 할애하지 않아도됩니다.
Kevin Cathcart 2011-06-17

23
@Xaqron : "시간 제한이있는 데이터"라는 개념이 프로그램에서 일반적이라는 것을 알게되면 메서드가 TimeLimited <string> 또는 TimeLimited를 반환하도록 할 수 있도록 일반 유형 "TimeLimited <T>"를 만드는 것을 고려할 수 있습니다. <우리> 또는 뭐든지. TimeLimited <T> 클래스는 "얼마나 오래 남았습니까?"를 알려주는 도우미 메서드를 가질 수 있습니다. 또는 "만료 되었습니까?" 또는 무엇이든. 유형 시스템에서 이와 같은 흥미로운 의미를 포착 해보십시오.
Eric Lippert

3
절대적으로, 나는 결코 튜플을 공용 인터페이스의 일부로 사용하지 않을 것입니다. 그러나 'private'코드의 경우에도 Tuple을 사용하는 대신 적절한 유형에서 엄청난 가독성을 얻습니다 (특히 Auto Properties로 private 내부 유형을 만드는 것이 얼마나 쉬운 지).
SolutionYogi

2
수집 압력은 무엇을 의미합니까?
롤백

27

이전 답변에 추가하면 C # 7은 값 유형 튜플을 제공합니다. System.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

4
그 성능이 매개 변수를 사용하지 않는 것과 어떻게 비교되는지 알고 있습니까?
SpaceMonkey

20

대답은 함수가하는 일의 의미와 두 값 사이의 관계에 달려 있다고 생각합니다.

예를 들어, TryParse메서드 out는 파싱 된 값을 받아들이는 매개 변수를 사용 bool하고 파싱이 성공했는지 여부를 나타내는 a 를 반환합니다 . 두 값은 실제로는 함께 속하지 않으므로 의미 상 더 의미가 있으며 out매개 변수 를 사용하는 코드의 의도를 더 쉽게 읽을 수 있습니다 .

그러나 함수가 화면에있는 일부 개체의 X / Y 좌표를 반환하는 경우 두 값이 의미 상 함께 속하므로 struct.

나는 개인적으로 tuple멤버를 검색하는 어색한 구문 때문에 외부 코드에 표시되는 모든 것에 대해 사용하는 것을 피할 것입니다.


의미론의 경우 +1. 당신의 대답은 우리가 out매개 변수를 떠날 수있을 때 더 적절한 whit 참조 유형 null입니다. nullable 불변 유형이 몇 가지 있습니다.
Xaqron

3
실제로 TryParse의 두 값은 하나를 반환 값으로 사용하고 다른 하나를 ByRef 매개 변수로 사용하여 암시하는 것보다 훨씬 더 많이 함께 속합니다. 여러면에서 논리적으로 반환되는 것은 nullable 형식입니다. TryParse 패턴이 잘 작동하는 경우도 있고 고통스러운 경우도 있습니다 ( "if"문에서 사용할 수 있다는 점이 좋지만 nullable 값을 반환하거나 기본값을 지정할 수있는 경우가 많습니다. 더 편리 할 것입니다).
supercat 2011-06-20

@supercat 나는 앤드류와 동의 할 것이고, 그들은 함께 속하지 않는다. 그들이 관련되어 있지만 반환은 함께 처리해야 할 것이 아닌 값을 전혀 신경 쓸 필요가 있는지 여부를 알려줍니다. 따라서 반환을 처리 한 후에는 출력 값과 관련된 다른 처리에 더 이상 필요하지 않습니다. 이는 키와 값 사이에 명확하고 진행중인 링크가있는 사전에서 KeyValuePair를 반환하는 것과는 다릅니다. 비록 nullable 형식이 .Net 1.1에 있었다면 동의하지만 null은 값을 표시하지 않는 적절한 방법이 될 것이므로 아마 사용했을 것입니다
MikeT

@MikeT : Microsoft가 구조가 단일 값을 나타내는 것에 대해서만 사용되어야한다고 제안한 것은 매우 불행한 일이라고 생각합니다. 사실 노출 된 필드 구조가 덕트 테이프와 함께 결합 된 독립 변수 그룹을 함께 전달하는 데 이상적인 매체 일 때 . 성공 표시기와 값은 반환되는 순간에 함께 의미 가 있습니다. 그 이후에는 별도의 변수로 더 유용 할 것입니다. 변수에 저장된 노출 필드 구조의 필드는 자체적으로 별도의 변수로 사용할 수 있습니다. 어쨌든 ...
supercat 2013-10-30

@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). 매개 변수 TValue로 사용되는 경우 이러한 분산은 불가능합니다 ref.
supercat 2013-10-30

2

두 번째 방법에서는 Tuple 클래스를 생성하고 객체를 생성 한 다음 여기에 값을 추가해야하므로 Out 매개 변수를 사용하는 방법을 사용하겠습니다.이 작업은 out 매개 변수에서 값을 반환하는 것보다 비용이 많이 드는 작업이라고 생각합니다. 튜플 클래스에서 여러 값을 반환하려는 경우 (실제로 하나의 매개 변수를 반환하여 수행 할 수 없음) 두 번째 방법을 사용합니다.


동의합니다 out. 또한 params질문을 똑바로 유지하기 위해 언급하지 않은 키워드가 있습니다.
Xaqron

2

구조체 대신 사용자 지정 클래스가있는 옵션을 하나 더 언급하지 않았습니다. 데이터에 함수에 의해 작동 될 수있는 관련 의미가 있거나 인스턴스 크기가 충분히 큰 경우 (일반적으로> 16 바이트) 사용자 지정 클래스가 선호 될 수 있습니다. 포인터에 대한 연관성과 참조 유형의 작동 방식을 이해해야하므로 공용 API에서 "out"을 사용하지 않는 것이 좋습니다.

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

튜플은 내부 사용에는 좋지만 공용 API에서는 사용이 어색합니다. 그래서 내 투표는 공용 API에 대한 구조체와 클래스 사이에 있습니다.


1
유형이 순전히 값 집계를 반환하기위한 목적으로 존재한다면, 단순한 노출 필드 값 유형이 이러한 의미에 가장 적합하다고 말하고 싶습니다. 필드 이외의 유형에 아무것도 없으면 캡처 된 뷰를 나타내는 지 라이브 뷰를 나타내는 지 (오픈 필드 구조는 작동 할 수 없음) 어떤 종류의 데이터 유효성 검사를 수행하는지 (분명히 없음)에 대한 질문이 없습니다. 불변 클래스는 작업하기가 덜 편리하며 인스턴스를 여러 번 전달할 수있는 경우에만 성능 이점을 제공합니다.
supercat

1

"모범 사례"는 없습니다. 그것은 당신이 편안하고 당신의 상황에서 가장 잘 작동하는 것입니다. 이것과 일치하는 한 게시 한 솔루션에 문제가 없습니다.


4
물론 그들은 모두 작동합니다. 기술적 인 이점이없는 경우에도 전문가가 주로 사용하는 것이 무엇인지 궁금합니다.
Xaqron
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.