C #이 참조 반환을 지원하지 않는 이유는 무엇입니까?


141

.NET은 참조 반환을 지원하지만 C #은 지원하지 않는다는 것을 읽었습니다. 특별한 이유가 있습니까? 왜 내가 다음과 같은 일을 할 수 없습니까?

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

8
음 .. 인용을 부탁드립니다.
RPM1984

5
C #에는 값과 참조 유형이 있으며 둘 다 무슨 뜻인지 반환 할 수 있습니다.
재실행

내가 좋아하는 뭔가에 대해 이야기하고있다return ref x
톰 Sarduy

2
4 년 후, 당신은 정확하게 이것을 할 수 있습니다 C# 7:)
Arghya C

답변:


188

이 질문은 2011 년 6 월 23 일제 블로그 의 주제였습니다 . 좋은 질문 감사합니다!

C # 팀은 C # 7에서이를 고려하고 있습니다. 자세한 내용은 https://github.com/dotnet/roslyn/issues/5233 을 참조하십시오.

업데이트 :이 기능은 C # 7에 도입되었습니다!


당신이 올바른지; .NET은 변수에 대한 관리되는 참조를 반환 하는 메서드를 지원 합니다. .NET은 다른 변수에 대한 관리 참조가 포함 된 로컬 변수 도 지원 합니다. 그러나 .NET은 가비지 수집 스토리를 지나치게 복잡하게하므로 다른 변수에 대한 관리되는 참조가 포함 된 필드배열 을 지원하지 않습니다 . 또한 "변수에 대한 관리되는 참조"형식은 object로 변환 할 수 없으므로로 사용할 수 없습니다. 제네릭 형식 또는 메서드에 대한 형식 인수)

어떤 이유로 논평자 "RPM1984"는이 사실에 대한 인용을 요구했다. RPM1984 .NET의이 기능에 대한 정보는 CLI 스펙 파티션 I 섹션 8.2.1.1, "관리 포인터 및 관련 유형"을 읽으십시오.

이 두 기능을 모두 지원하는 C # 버전을 만드는 것은 전적으로 가능합니다. 그런 다음과 같은 일을 할 수 있습니다

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

그리고 그것을 호출

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

나는 이러한 기능을 지원하는 C 번호의 버전을 구축하는 것이 가능하다는 것을 경험적으로 알고 내가 그렇게 때문에를 . 고급 프로그래머, 특히 관리되지 않는 C ++ 코드를 이식하는 사람들은 실제로 포인터를 사용하고 메모리를 고정시키는 데 큰 어려움을 겪지 않고 참조로 작업을 수행하는 더 많은 C ++과 같은 기능을 요구합니다. 관리되는 참조를 사용하면 가비지 수집 성능을 저하시키는 비용을 지불하지 않고도 이러한 이점을 얻을 수 있습니다.

우리는이 기능을 고려하여 실제로 다른 내부 팀에 피드백을받을 수있을 정도로 충분히 구현했습니다. 그러나 현재 우리의 연구에 따르면이 기능은 실제로 지원되는 언어 기능으로 만들기에 충분한 호소력이나 사용 사례가 충분하지 않다고 생각합니다 . 우선 순위가 높고 시간과 노력이 제한되어 있으므로이 기능을 곧 사용할 수 없습니다.

또한 제대로 수행하려면 CLR을 약간 변경해야합니다. 현재 CLR은 이러한 상황을 감지하는 검출기가 없기 때문에 환급 방법을 합법적 이지만 검증 할 수없는 것으로 취급합니다.

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3은 M2 로컬 변수의 내용을 반환하지만 해당 변수의 수명이 끝났습니다! 스택 안전 을 명확하게 위반 하지 않는 참조 반환의 사용을 결정하는 검출기를 작성할 수 있습니다 . 우리가 할 일은 그러한 검출기를 작성하는 것이며, 검출기가 스택 안전을 증명할 수 없다면 프로그램의 해당 부분에서 ref return을 사용할 수 없습니다. 그렇게하는 것은 엄청난 양의 개발 작업이 아니지만 테스트 팀이 실제로 모든 상황을 해결하도록하는 것은 많은 부담입니다. 기능의 비용이 현재의 이점보다 비용을 능가하지 않는 수준으로 증가시키는 것은 또 다른 일입니다.

이 기능이 왜 필요한지 설명해 주시면 감사하겠습니다 . 실제 고객이 원하는 이유에 대한 정보가 많을수록 언젠가는 제품에 정보를 제공 할 가능성이 높아집니다. 귀여운 작은 기능이며 충분한 관심이 있다면 어떻게 든 고객에게 제공 할 수 있기를 바랍니다.

(또한 관련 질문을 참조 는 C #에서 변수에 대한 참조를 반환 할 수 있습니까? 그리고 ++ 나 C와 같은 C #을 함수 내에서 참조를 사용할 수 있습니까? )


4
@EricLippert : 설득력있는 예는 없습니다. 우수하고 매력적인 답변
Tom Sarduy

1
@ 에릭 : 당신의 외향에서 돌아온 후 y 살아 유지하는 것이 더 적합하지 M2않을까요? 나는이 기능이 람다를 현지인을 사로 잡는 것처럼 작동 할 것으로 기대합니다. 아니면 CLR이 해당 시나리오를 처리하는 방식이기 때문에 제안한 동작입니까?
Fede

3
@Eric : IMHO는 속성이 값 형식에 대한 참조를 반환하도록하는 기능은 .net 언어의 주요 생략입니다. Arr이 값 유형 (예 : Point)의 배열이면 Arr (3) .X = 9라고 말하고 Arr (9) .X 또는 SomeOtherArray (2)의 값을 변경하지 않았 음을 알 수 있습니다. 엑스; Arr이 대신 참조 유형의 배열 인 경우, 그러한 보장은 존재하지 않습니다. 배열의 인덱싱 연산자가 참조를 반환한다는 사실은 매우 유용합니다. 다른 유형의 컬렉션에서는 이러한 기능을 제공 할 수없는 것이 유감입니다.
supercat

4
에릭, 시나리오에 대한 피드백을 제공하는 가장 좋은 방법은 무엇입니까? MS 연결? UserVoice (임의의 10 포스트 / 투표 제한)? 다른 것?
로마 Starkov

1
@ThunderGr : 이것이 "안전하지 않은"에 대한 C # 철학입니다. 메모리에 안전하지 않은 코드를 작성하는 경우 C #에서는 "안전하지 않은"것으로 표시하여 메모리 안전에 대한 책임을 져야합니다. 문제의 변수가 관리되지 않는 유형 인 경우 C #에는 안전하지 않은 기능 버전이 이미 있습니다. 문제는 C # 팀이 관리되는 유형을 처리하는 안전하지 않은 버전을 만들어야하는지 여부입니다. 그렇게함으로써 개발자가 끔찍한 버그를 쉽게 작성할 수 있다면 일어나지 않을 것입니다. C #은 끔찍한 버그를 쉽게 작성할 수있는 언어 인 C ++이 아닙니다. C #은 설계 상 안전합니다.
Eric Lippert

21

값 유형에 대한 참조를 리턴하는 메소드에 대해 이야기하고 있습니다. 내가 아는 C #의 내장 예제는 값 유형의 배열 접근 자입니다.

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

이제 해당 구조체의 배열을 만듭니다.

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

이 경우 points[0]배열 인덱서 는 struct에 대한 참조를 반환합니다. 이와 같은 "참조 반환"동작을 갖는 고유 인덱서 를 작성하는 것은 불가능합니다 (예 : 사용자 지정 컬렉션의 경우).

나는 C # 언어를 디자인하지 않았기 때문에 그것을 지원하지 않는 모든 추론을 알지 못했지만 짧은 대답은 다음과 같습니다. 우리는 그것을하지 않고 잘 지낼 수 있습니다.


8
Tom은 변수에 대한 참조 반환하는 메서드에 대해 묻고 있습니다. 물론 변수는 값 유형이 될 필요는 없지만 물론 사람들이 참조 반환 방법을 원할 때 원하는 것입니다. 그렇지 않으면 훌륭한 분석입니다. C # 언어에서 복잡한식이 사용자가 조작 할 수있는 변수에 대한 참조 를 생성하는 유일한 위치 는 배열 인덱서라는 것이 맞습니다. (물론 수신자와 필드 사이의 멤버 액세스 연산자 "."이지만 변수에 대한 액세스는 분명합니다.)
Eric Lippert

1

당신은 항상 다음과 같은 것을 할 수 있습니다 :

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.