할당 할 필요가없는 구조체 유형의 매개 변수


9

코드 검토 중에 실수로 함수에서 한 줄을 주석 처리 할 때 코드에서 기괴한 동작을 발견했습니다. 재현하기는 매우 어려웠지만 여기서는 비슷한 예를 보여 드리겠습니다.

이 테스트 클래스가 있습니다.

public class Test
{
    public void GetOut(out EmailAddress email)
    {
        try
        {
            Foo(email);
        }
        catch
        {
        }
    }

    public void Foo(EmailAddress email)
    {
    }
}

GetOut일반적으로 오류가 발생하는 이메일에 할당이 없습니다 .

제어가 현재 메소드를 떠나기 전에 out 매개 변수 'email'을 지정해야합니다.

그러나 EmailAddress가 별도 어셈블리의 구조체에 있으면 오류가 생성되지 않고 모든 것이 잘 컴파일됩니다.

public struct EmailAddress
{
    #region Constructors

    public EmailAddress(string email)
        : this(email, string.Empty)
    {
    }

    public EmailAddress(string email, string name)
    {
        this.Email = email;
        this.Name = name;
    }

    #endregion

    #region Properties

    public string Email { get; private set; }
    public string Name { get; private set; }

    #endregion
}

컴파일러가 이메일을 할당해야하는 이유는 무엇입니까? 구조체가 별도의 어셈블리에서 생성 된 경우이 코드가 컴파일되는 이유는 무엇입니까? 그러나 구조체가 기존 어셈블리에 정의되어 있으면 컴파일되지 않습니다.


2
클래스를 사용하는 경우 객체의 인스턴스를 '새로 만들어야'합니다. 구조체에는 필요하지 않습니다. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/... (이 특별히 해당 페이지의 텍스트를 검색 : 달리 클래스, 구조체는 새로운 operato를 사용하지 않고 인스턴스화 할 수있다)
Dortimer

1
즉시 당신의 개 구조체 변수를수록 그것은 :) 컴파일되지 않습니다
앙드레 샌슨

이 예제에서는 with struct Dog{}가 모두 좋습니다.
Henk Holterman

2
@ johnny5 그런 다음 예제를 보여주십시오.
André Sanson

1
좋아, 재미있다. Core 3 Console 앱 및 .Standard 클래스 lib로 재생성되었습니다.
Henk Holterman

답변:


12

TLDR : 이것은 오랫동안 알려진 버그입니다. 나는 2010 년에 처음으로 썼습니다 :

https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/

무해하며 안전하게 무시할 수 있으며 다소 모호한 버그를 발견 한 것을 축하합니다.

컴파일러가이를 강제로 Email할당 하지 않는 이유는 무엇 입니까?

아, 그것은 패션에 있습니다. 우리가 볼 수 있듯이 변수가 확실히 할당되었음을 암시하는 조건에 대한 잘못된 생각이 있습니다.

구조체가 별도의 어셈블리에서 생성 된 경우이 코드가 컴파일되는 이유는 무엇입니까? 그러나 구조체가 기존 어셈블리에 정의되어 있으면 컴파일되지 않습니다.

이것이 버그의 핵심입니다. 버그는 C # 컴파일러가 구조체에서 명확한 할당 검사를 수행하는 방법과 컴파일러가 라이브러리에서 메타 데이터를로드하는 방법의 교차로 인한 결과입니다.

이걸 고려하세요:

struct Foo 
{ 
  public int x; 
  public int y; 
}
// Yes, public fields are bad, but this is just 
// to illustrate the situation.
void M(out Foo f)
{

좋아,이 시점에서 우리는 무엇을 알고 있는가? f은 변수 유형의 별칭 Foo이므로 저장소가 이미 할당되었으며 적어도 저장소 할당 자에서 나온 상태에 있습니다. 호출자가 변수에 배치 한 값이 있으면 해당 값이 있습니다.

우리는 무엇을 요구합니까? 우리는 f통제가 M정상적으로 떠나는 지점에 반드시이를 할당 해야 합니다. 따라서 다음과 같은 것을 기대할 수 있습니다.

void M(out Foo f)
{
  f = new Foo();
}

이는 세트 f.xf.y기본값으로. 하지만 이건 어때?

void M(out Foo f)
{
  f = new Foo();
  f.x = 123;
  f.y = 456;
}

그것도 괜찮을 것입니다. 그러나 여기에 키커가 있습니다. 왜 우리가 기본값을 나중에 나중에 날려 버릴 수 있도록 할당해야합니까? C #의 명확한 할당 검사기는 모든 필드 가 할당 되었는지 확인합니다 ! 이것은 합법적입니다.

void M(out Foo f)
{
  f.x = 123;
  f.y = 456;
}

왜 합법적이지 않아야합니까? 가치 유형입니다. f변수이고 이미 유효한 type 값이 포함되어 Foo있으므로 필드를 설정하면됩니다.

권리. 버그는 무엇입니까?

발견 한 버그 는 비용 절감으로 C # 컴파일러는 참조 라이브러리에있는 구조체의 개인 필드에 대한 메타 데이터를로드하지 않습니다 . 이 메타 데이터는 매우 클 수 있으며 매번 메모리에 모두로드하는 데 거의 이기지 않기 때문에 컴파일러 속도가 느려집니다.

이제 발견 한 버그의 원인을 추론 할 수 있습니다. 컴파일러가 out 매개 변수가 확실히 할당되었는지 확인하면 알려진 필드 수명확한 초기화 된 필드 수를 비교 하며 개인 필드 메타 데이터가로드되지 않았기 때문에 공개 필드 0에 대해서만 알고 있습니다. . 컴파일러는 "제로 필드 필요, 제로 필드 초기화, 우리는 좋다"고 결론 지었다.

내가 말했듯이,이 버그는 10 년 이상 주변에 있었고 당신과 같은 사람들은 때때로 그것을 재발견하고보고합니다. 그것은 무해하며, 그것을 고치는 것이 거의 제로의 이점이 있지만 큰 비용이 들기 때문에 고칠 것 같지 않습니다.

물론 컴파일러는 이미 개인 필드에 대한 정보를 가지고 있기 때문에 프로젝트의 소스 코드에있는 개인 구조체의 필드에 대해 버그를 재현하지 않습니다.


@ johnny5 : 오류가 발생하지 않아야합니다. dotnetfiddle.net/ZEKiUk를 참조하십시오 . 간단한 재현을 게시 할 수 있습니까?
Eric Lippert

1
바이올린 덕분에 x와 y가 멤버 대신 속성으로 정의 되었기 때문에
johnny 5

1
@ johnny5 : 방금 일반 C # 1.0 스타일 속성을 정의한 경우 명확한 할당 검사기의 관점에서 보면 필드가 아닌 메서드입니다. C # 3.0+ 스타일 자동 속성을 정의한 경우 컴파일러는이를 지원하는 개인 필드가 있음을 알고 있습니다. 그 물건의 명확한 할당에 대한 규칙은 수년에 걸쳐 조정되었으며 이제는 정확한 규칙을 기억하지 않습니다.
Eric Lippert

System.TimeSpan대신 사용 하면 오류가 발생합니다. error CS0269: Use of unassigned out parameter 'email'error CS0177: The out parameter 'email' must be assigned to before control leaves the current method. 단 하나의 비 정적 필드가 TimeSpan_ticks. 그것은이다 internal의 조립 mscorlib에에. 이 어셈블리는 특별합니까? 와 동일 System.DateTime하며 해당 필드는 다음과 같습니다private
Jeppe Stig Nielsen

@JeppeStigNielsen : 그게 무슨 일인지 모르겠어요! 알아 내면 알려주세요.
Eric Lippert

1

버그처럼 보이지만 의미가 있습니다.

'결측 오류'는 클래스 라이브러리를 사용할 때만 나타납니다. 그리고 클래스 라이브러리는 VB.Net과 같은 다른 .net 언어로 작성되었을 수 있습니다. '확정 할당 추적'은 프레임 워크가 아닌 C #의 기능입니다.

전체적으로 나는 그것이 버그라고 생각하지 않지만 이것에 대한 권위있는 진술에 대해서는 모른다.


C #으로 어셈블리를 가져 오는 경우 구조체가 다른 언어로 작성된 어셈블리에있을 수 있지만 해당 코드를 사용하는 코드는 여전히 C #이므로 명확한 할당 추적을 사용할 필요는 없습니다.
johnny 5

1
반드시 그런 것은 아닙니다. C #에서는 단위 화 된 (로컬) 변수를 사용할 수 없지만 동시에 프레임 워크는 0 ( default(T)) 으로 설정되도록 보장합니다 . 따라서 메모리 안전 또는 이와 유사한 것이 위반되지 않습니다.
Henk Holterman

3
나는 그런 권위있는 진술을 할 수 있습니다. :) 오랫동안 알려진 버그입니다.
Eric Lippert

1
@EricLippert 감사합니다, 당신은 언젠가 이것을 볼 수 있기를 바랐습니다
johnny 5
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.