IT story

.NET에서 구조체의 기본 생성자를 정의 할 수없는 이유는 무엇입니까?

hot-time 2020. 4. 11. 10:15
반응형

.NET에서 구조체의 기본 생성자를 정의 할 수없는 이유는 무엇입니까?


.NET에서 값 유형 (C # struct)은 매개 변수가없는 생성자를 가질 수 없습니다. 이 게시물 에 따르면 이것은 CLI 사양에 의해 요구됩니다. 모든 값 유형에 대해 기본 생성자가 (컴파일러에 의해) 생성되어 모든 멤버를 0으로 초기화합니다 (또는 null).

이러한 기본 생성자를 정의 할 수없는 이유는 무엇입니까?

간단한 사용은 합리적인 숫자입니다.

public struct Rational {
    private long numerator;
    private long denominator;

    public Rational(long num, long denom)
    { /* Todo: Find GCD etc. */ }

    public Rational(long num)
    {
        numerator = num;
        denominator = 1;
    }

    public Rational() // This is not allowed
    {
        numerator = 0;
        denominator = 1;
    }
}

현재 버전의 C #을 사용하면 기본 Rational이 0/0그리 좋지 않습니다.

추신 : 기본 매개 변수는 C # 4.0 에서이 문제를 해결하는 데 도움이됩니까? 또는 CLR 정의 기본 생성자가 호출됩니까?


Jon Skeet 가 대답했습니다.

당신의 모범을 사용하기 위해 누군가가했을 때 어떤 일이 일어나고 싶습니까?

 Rational[] fractions = new Rational[1000];

생성자를 1000 번 실행해야합니까?

물론, 이것이 기본 생성자를 처음에 쓴 이유입니다. 명시 적 기본 생성자가 정의되지 않은 경우 CLR은 기본 제로 생성자를 사용해야합니다 . 그렇게하면 사용한만큼만 지불합니다. 그런 다음 기본이 아닌 1000 개의 컨테이너를 Rational원하고 1000 구성을 최적화하려는 List<Rational>경우 배열 대신 사용합니다 .

내 생각에이 이유는 기본 생성자의 정의를 막기에 충분하지 않다.


참고 : 아래의 대답은 구조체에 선언 매개 변수가없는 생성자하는 기능을 소개 할 계획이다 C # 6, 이전에 오랜 시간이 기록 된 - (예를 들어 배열 생성을 위해) 모든 상황에서하지만 그들은 여전히 호출되지 않습니다 결국 ( 이 기능 은 C # 6에 추가되지 않았습니다 .


편집 : Grauenwolf의 CLR에 대한 통찰력으로 인해 아래 답변을 편집했습니다.

CLR을 사용하면 값 형식에 매개 변수가없는 생성자가있을 수 있지만 C #에는 없습니다. 나는 이것이 생성자가 그렇지 않을 때 호출 될 것이라는 기대를 불러 일으키기 때문이라고 생각합니다. 예를 들어 다음을 고려하십시오.

MyStruct[] foo = new MyStruct[1000];

CLR은 적절한 메모리를 할당하고 모두 제로화함으로써이 작업을 매우 효율적으로 수행 할 수 있습니다. MyStruct 생성자를 1000 번 실행해야한다면 효율성이 훨씬 떨어집니다. (- 당신이 경우에 실제로, 그렇지 않습니다 매개 변수없는 생성자가 당신이 배열을 만들 때이 실행되지 않습니다, 또는 초기화되지 않은 인스턴스 변수가있을 때.)

C #의 기본 규칙은 "모든 유형의 기본값은 초기화에 의존 할 수 없습니다"입니다. 이제 그들은 수있는 매개 변수가없는 생성자를 정의 할 수 있습니다,하지만 생성자는 모든 경우에 수행 될 필요는 없다 -하지만 더 혼란을 주도했을 것이다. (또는 적어도 논쟁의 여지가 있다고 생각합니다.)

편집 : 귀하의 예를 사용하기 위해 누군가가했을 때 어떤 일을하고 싶습니까?

Rational[] fractions = new Rational[1000];

생성자를 1000 번 실행해야합니까?

  • 그렇지 않다면, 1000 개의 유효하지 않은 합리적 결과를 얻게됩니다
  • 그렇다면 배열을 실제 값으로 채우려 고하면 많은 작업을 낭비했을 것입니다.

편집 : (질문이 조금 더 있습니다) 매개 변수가없는 생성자는 컴파일러에 의해 생성되지 않습니다. 이 밝혀 있지만,이 - 값 유형은 멀리 CLR에 관한 한 같은 생성자를 할 필요는 없습니다 는 일리노이을 작성하는 경우. new Guid()C #에서 " " 를 작성할 때 일반 생성자를 호출하면 얻을 수있는 것과 다른 IL을 방출합니다. 해당 측면에 대한 자세한 내용은 이 SO 질문참조하십시오 .

나는 의심 매개 변수가없는 생성자와 프레임 워크의 모든 값 유형이 아니라는 것을. 의심 할 여지없이 NDepend가 충분히 훌륭하게 요구했는지 말해 줄 수 있습니다 ... C #이 금지한다는 사실은 아마도 그것이 나쁜 생각이라고 생각하기에 충분히 큰 힌트 일 것입니다.


구조체는 값 형식이며 값 형식은 선언되는 즉시 기본값을 가져야합니다.

MyClass m;
MyStruct m2;

인스턴스화하지 않고 위와 같이 두 개의 필드를 선언하면 디버거를 중단하면 mnull이되지만 m2그렇지 않습니다. 이것을 감안할 때 매개 변수가없는 생성자는 의미가 없습니다. 실제로 구조체의 모든 생성자는 값을 할당하는 것입니다. 실제로 m2는 위의 예에서 아주 행복하게 사용될 수 있으며, 해당되는 경우 해당 메소드와 필드 및 속성을 조작 할 수 있습니다!


CLR에서 허용하지만 C #에서는 구조체에 기본 매개 변수없는 생성자가있을 수 없습니다. 그 이유는 값 유형의 경우 기본적으로 컴파일러가 기본 생성자를 생성하지 않으며 기본 생성자에 대한 호출도 생성하지 않기 때문입니다. 따라서 기본 생성자를 정의한 경우에도 호출되지 않으며 혼동 될뿐입니다.

이러한 문제를 피하기 위해 C # 컴파일러는 사용자가 기본 생성자를 정의 할 수 없습니다. 또한 기본 생성자를 생성하지 않으므로 필드를 정의 할 때 필드를 초기화 할 수 없습니다.

또는 큰 이유는 구조가 값 유형이고 값 유형이 기본값으로 초기화되고 생성자가 초기화에 사용되기 때문입니다.

new키워드로 구조체를 인스턴스화하지 않아도됩니다 . 대신 int처럼 작동합니다. 직접 액세스 할 수 있습니다.

Structs에는 명시적인 매개 변수없는 생성자가 포함될 수 없습니다. 구조 부재는 자동으로 기본값으로 초기화됩니다.

구조체의 기본 (매개 변수가없는) 생성자는 예상치 못한 동작 인 0으로 끝나는 상태와 다른 값을 설정할 수 있습니다. 따라서 .NET 런타임은 구조체의 기본 생성자를 금지합니다.


기본 "합리적"숫자를 초기화하고 반환하는 정적 속성을 만들 수 있습니다.

public static Rational One => new Rational(0, 1); 

그리고 그것을 다음과 같이 사용하십시오 :

var rat = Rational.One;

더 짧은 설명 :

C ++에서 struct와 class는 같은 동전의 양면에 불과했습니다. 유일한 차이점은 하나는 기본적으로 공개되었고 다른 하나는 비공개였습니다.

.NET 에서는 구조체와 클래스 사이에 훨씬 큰 차이가 있습니다. 가장 중요한 것은 구조체가 값 형식 의미를 제공하는 반면 클래스는 참조 형식 의미를 제공한다는 것입니다. 이 변경의 의미에 대해 생각하기 시작하면 설명하는 생성자 동작을 포함하여 다른 변경도 더 의미가 있습니다.


특별한 경우입니다. 분자가 0이고 분모가 0이면 실제로 원하는 값을 갖는 것처럼 가장하십시오.


C #을 사용하고 있으므로 기본 생성자를 정의 할 수 없습니다.

Structs는 .NET에서 기본 생성자를 가질 수 있지만 지원하는 특정 언어는 알지 못합니다.


기본 생성자 딜레마가없는 솔루션이 있습니다. 나는 이것이 늦은 해결책이라는 것을 알고 있지만 이것이 해결책이라는 점은 주목할 가치가 있다고 생각합니다.

public struct Point2D {
    public static Point2D NULL = new Point2D(-1,-1);
    private int[] Data;

    public int X {
        get {
            return this.Data[ 0 ];
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 0 ] = value;
            }
        }
    }

    public int Z {
        get {
            return this.Data[ 1 ];
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 1 ] = value;
            }
        }
    }

    public Point2D( int x , int z ) {
        this.Data = new int[ 2 ] { x , z };
    }

    public static Point2D operator +( Point2D A , Point2D B ) {
        return new Point2D( A.X + B.X , A.Z + B.Z );
    }

    public static Point2D operator -( Point2D A , Point2D B ) {
        return new Point2D( A.X - B.X , A.Z - B.Z );
    }

    public static Point2D operator *( Point2D A , int B ) {
        return new Point2D( B * A.X , B * A.Z );
    }

    public static Point2D operator *( int A , Point2D B ) {
        return new Point2D( A * B.Z , A * B.Z );
    }

    public override string ToString() {
        return string.Format( "({0},{1})" , this.X , this.Z );
    }
}

get; set;을 사용하여 null이라는 정적 구조체가 있다는 사실을 무시하고 (참고 : 이것은 모든 양의 사분면에만 해당) get; set; C #에서는 특정 데이터 형식이 기본 생성자 Point2D ()에 의해 초기화되지 않은 오류를 처리하기 위해 try / catch / finally를 가질 수 있습니다. 나는 이것이이 답변에 대한 일부 사람들에게 해결책으로 애매하다고 생각합니다. 그게 주로 내가 추가하는 이유입니다. C #에서 getter 및 setter 기능을 사용하면이 기본 생성자 인 non-sense를 무시하고 초기화하지 않은 것을 시도 할 수 있습니다. 나를 위해 이것은 잘 작동합니다. 다른 사람을 위해 if 문을 추가하고 싶을 수도 있습니다. 따라서 Numerator / Denominator 설정을 원할 경우이 코드가 도움이 될 수 있습니다. 이 솔루션이 멋지게 보이지 않고 효율성 측면에서 더 나쁘게 작동한다고 반복하고 싶습니다. 이전 버전의 C #에서 온 누군가를 위해 배열 데이터 형식을 사용하면이 기능이 제공됩니다. 작동하는 것을 원한다면 다음을 시도하십시오.

public struct Rational {
    private long[] Data;

    public long Numerator {
        get {
            try {
                return this.Data[ 0 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 0 ];
            }
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 0 ] = value;
            }
        }
    }

    public long Denominator {
        get {
            try {
                return this.Data[ 1 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 1 ];
            }
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 1 ] = value;
            }
        }
    }

    public Rational( long num , long denom ) {
        this.Data = new long[ 2 ] { num , denom };
        /* Todo: Find GCD etc. */
    }

    public Rational( long num ) {
        this.Data = new long[ 2 ] { num , 1 };
        this.Numerator = num;
        this.Denominator = 1;
    }
}

나는 내가 줄 늦은 솔루션과 동등한 것을 보지 못 했으므로 여기에 있습니다.

오프셋을 사용하여 기본값 0에서 원하는 값으로 값을 이동하십시오. 여기서는 필드에 직접 액세스하는 대신 속성을 사용해야합니다. (가능한 c # 7 기능을 사용하면 속성 범위 필드를 더 잘 정의하여 코드에서 직접 액세스되지 않도록 할 수 있습니다.)

이 솔루션은 값 유형 만있는 간단한 구조체 (ref 유형 또는 nullable 구조체 없음)에 작동합니다.

public struct Tempo
{
    const double DefaultBpm = 120;
    private double _bpm; // this field must not be modified other than with its property.

    public double BeatsPerMinute
    {
        get => _bpm + DefaultBpm;
        set => _bpm = value - DefaultBpm;
    }
}

이것은 다른 것보다 이 방법은 특별한 케이스하지만이 모든 범위에 대해 작동되는 오프셋 사용하지 않는,이 답변.

열거 형을 필드로 사용하는 예입니다.

public struct Difficaulty
{
    Easy,
    Medium,
    Hard
}

public struct Level
{
    const Difficaulty DefaultLevel = Difficaulty.Medium;
    private Difficaulty _level; // this field must not be modified other than with its property.

    public Difficaulty Difficaulty
    {
        get => _level + DefaultLevel;
        set => _level = value - DefaultLevel;
    }
}

내가 말했듯이 struct에 값 필드 만있는 경우 에도이 트릭이 모든 경우에 작동하지는 않을 수 있습니다. 귀하의 경우에 효과가 있는지 여부 만 알 수 있습니다. 그냥 검사하십시오. 그러나 당신은 일반적인 생각을 얻습니다.


public struct Rational 
{
    private long numerator;
    private long denominator;

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}

참고 URL : https://stackoverflow.com/questions/333829/why-cant-i-define-a-default-constructor-for-a-struct-in-net

반응형