.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;
인스턴스화하지 않고 위와 같이 두 개의 필드를 선언하면 디버거를 중단하면 m
null이되지만 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;
}
}
'IT story' 카테고리의 다른 글
Mathematica에서 맞춤형 배포를위한 NExpectation 최소화 (0) | 2020.04.11 |
---|---|
NoSQL 사용 사례 시나리오 또는 NoSQL을 사용하는시기 (0) | 2020.04.11 |
모든 http : // 링크를 // //로 변경할 수 있습니까? (0) | 2020.04.11 |
Mercurial에서 기능 분기를 올바르게 닫는 방법은 무엇입니까? (0) | 2020.04.11 |
SQL 문을 sargable로 만드는 것은 무엇입니까? (0) | 2020.04.11 |