에서 구조체의 기본 생성자를 정의할 수 없는 이유는 무엇입니까?NET?
값 입니다.NET에서 값 유형(C#)struct)에는 매개 변수가 없는 생성자가 있을 수 없습니다.이 게시물에 따르면 이는 CLI 사양에 따라 의무화됩니다.모든 값 유형에 대해 모든 구성원을 0(또는 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#을 은 현재버의전 C#는은기하본용다같다습니음과사을입니다.0/0별로 멋지지 않은.
PS: C# 4.0에 대한 기본 매개 변수가 이 문제를 해결하는 데 도움이 될까요, 아니면 CLR 정의 기본 생성자가 호출될까요?
존 스키트는 이렇게 대답했습니다.
예를 들어, 다른 사용자가 수행한 경우 수행할 작업은 다음과 같습니다.
Rational[] fractions = new Rational[1000];생성자를 1000번 통과해야 합니까?
당연히 그래야 합니다. 그래서 처음에 기본 생성자를 작성했습니다.CLR은 명시적인 기본 생성자가 정의되지 않은 경우 기본 제로잉 생성자를 사용해야 합니다. 그렇게 하면 사용자가 사용한 것에 대해서만 비용을 지불할 수 있습니다.그러면 기본값이 아닌 1000개의 컨테이너를 원한다면,Rational구조를 ) 는 (으)로 사용할 것입니다.List<Rational>배열보다는
이러한 이유는 기본 생성자를 정의할 수 없을 정도로 강력하지 않습니다.
참고: 아래 답변은 C# 6보다 오래 전에 작성되었습니다. C# 6은 구조체에 매개 변수가 없는 생성자를 선언하는 기능을 도입할 계획입니다. 그러나 모든 상황에서 여전히 호출되지는 않습니다(예: 배열 생성). (결국 이 기능은 C#6에 추가되지 않았습니다.)
편집: CLR에 대한 Grauenwolf의 통찰력으로 아래 답변을 편집했습니다.
CLR을 사용하면 값 유형에 매개 변수 없는 생성자를 사용할 수 있지만 C#은 그렇지 않습니다.시공자가 불리지 않을 때 불리게 될 것이라는 기대감을 심어줄 수 있기 때문이라고 생각합니다.예를 들어, 다음과 같이 생각해 보십시오.
MyStruct[] foo = new MyStruct[1000];
CLR은 적절한 메모리를 할당하고 모두 0으로 설정하는 것만으로도 매우 효율적으로 이를 수행할 수 있습니다.MyStruct 생성자를 1000번 실행해야 한다면 훨씬 덜 효율적일 것입니다. (사실, 매개 변수가 없는 생성자가 있다면 배열을 만들 때나 초기화되지 않은 인스턴스 변수가 있을 때 실행되지 않습니다.)
C#의 기본 규칙은 "모든 유형의 기본값은 초기화에 의존할 수 없습니다."입니다.이제 그들은 매개 변수가 없는 생성자를 정의할 수 있었지만, 모든 경우에 생성자를 실행할 필요는 없었습니다. 하지만 그것은 더 많은 혼란을 초래했을 것입니다. (또는 적어도, 논쟁은 계속될 것이라고 믿습니다.)
편집: 예를 사용하려면 다른 사용자가 수행했을 때 수행할 작업을 선택하십시오.
Rational[] fractions = new Rational[1000];
생성자를 1000번 통과해야 합니까?
- 만약 그렇지 않다면, 우리는 1000개의 잘못된 이성을 갖게 될 것입니다.
- 그렇다면 어레이를 실제 값으로 채우려고 하면 많은 작업이 낭비될 수 있습니다.
편집: (질문에 조금 더 답변)매개 변수 없는 생성자가 컴파일러에 의해 생성되지 않았습니다.값 유형은 CLR에 관한 한 생성자를 가질 필요가 없지만 IL로 작성하면 생성자를 가질 수 있습니다.당신이 "라고 쓸 때new Guid()C#에서 일반 생성자를 호출하면 얻을 수 있는 것과 다른 IL을 방출합니다.이러한 측면에 대한 자세한 내용은 이 SO 질문을 참조하십시오.
매개 변수 없는 생성자가 있는 프레임워크에는 값 유형이 없는 것 같습니다.Ndepend가 내가 충분히 친절하게 요청했다면 나에게 말할 수 있었을 것입니다.C#에서 금지한다는 사실은 제가 그것이 아마도 나쁜 생각이라고 생각할 만큼 충분히 큰 힌트입니다.
구조체는 값 유형이며 값 유형은 선언되는 즉시 기본값을 가져야 합니다.
MyClass m;
MyStruct m2;
하지 않고 m이지만 null입니다.m2하지 않을 것이다.이것을 고려할 때, 매개 변수가 없는 생성자는 말이 안 됩니다. 사실 구조체의 모든 생성자는 값을 할당하는 것뿐입니다. 그 자체는 이미 존재합니다.실제로 m2는 위의 예에서 꽤 행복하게 사용될 수 있고, 만약 있다면, 그것의 메소드가 호출되고, 그것의 필드와 속성이 조작될 수 있습니다!
기본 "합리적" 숫자를 초기화하고 반환하는 정적 속성을 만들 수 있습니다.
public static Rational One => new Rational(0, 1);
다음과 같이 사용합니다.
var rat = Rational.One;
더 짧은 설명:
C++에서 구조와 클래스는 동일한 동전의 양면에 불과했습니다.유일한 실질적인 차이점은 하나는 기본적으로 공개적이고 다른 하나는 비공개적이라는 것입니다.
.NET에서는 구조체와 클래스 사이에 훨씬 더 큰 차이가 있습니다.중요한 것은 구조체는 가치형 의미론을 제공하는 반면 클래스는 참조형 의미론을 제공한다는 것입니다.이 변경의 영향에 대해 생각하기 시작하면 설명한 생성자 동작을 포함하여 다른 변경사항도 더 의미 있게 적용됩니다.
C# 10.0부터 다음을 수행할 수 있습니다.
저는 제가 제시할 늦은 해결책에 해당하는 것을 보지 못했습니다. 그래서 여기 있습니다.
오프셋을 사용하여 기본값 0에서 원하는 값으로 값을 이동합니다.여기서는 필드에 직접 액세스하는 대신 속성을 사용해야 합니다.(아마도 가능한 c#7 기능을 사용하면 속성 범위 필드를 더 잘 정의하여 코드에서 직접 액세스되지 않도록 보호할 수 있습니다.)
이 솔루션은 값 유형(참조 유형 또는 null 가능 구조체 없음)만 있는 단순 구조체에 대해 작동합니다.
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;
}
}
제가 말씀드렸듯이 이 트릭이 모든 경우에 효과가 없을 수도 있습니다. 구조가 값 필드만 가지고 있더라도, 당신의 경우에 효과가 있는지 아닌지는 당신만 알고 있습니다. 그냥 검토하십시오.하지만 당신은 대체적인 생각을 이해합니다.
그냥 특별한 경우로.분자가 0이고 분모가 0이면 원하는 값을 가진 것처럼 가정합니다.
제가 사용하는 것은 다음과 같은 백업 필드와 결합된 널 병합 연산자(??)입니다.
public struct SomeStruct {
private SomeRefType m_MyRefVariableBackingField;
public SomeRefType MyRefVariable {
get { return m_MyRefVariableBackingField ?? (m_MyRefVariableBackingField = new SomeRefType()); }
}
}
이것이 도움이 되길 바랍니다 ;)
참고: Null 병합 할당은 현재 C# 8.0에 대한 기능 제안입니다.
C#을 사용하고 있기 때문에 기본 생성자를 정의할 수 없습니다.
구조체에는 에 기본 생성자가 있을 수 있습니다.NET, 비록 저는 그것을 지원하는 어떤 특정한 언어에 대해서는 알지 못하지만요.
이에 대한 간단한 해결책을 찾았습니다.
struct Data
{
public int Point { get; set; }
public HazardMap Map { get; set; }
public Data Initialize()
{
Point = 1; //set anything you want as default
Map = new HazardMap();
return this;
}
}
코드에서는 다음을 수행합니다.
Data input = new Data().Initialize();
기본 생성자가 없는 딜레마에 대한 저의 해결책은 다음과 같습니다.이것이 늦은 해결책이라는 것을 알고 있지만, 이것이 해결책이라는 것에 주목할 가치가 있다고 생각합니다.
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 );
}
}
Null이라는 정적 구조를 가지고 있다는 사실을 무시합니다. (참고:이것은 모든 양의 사분면에만 해당), get;set을 사용하여, C#에서 특정 데이터 유형이 기본 생성자 Point2D()에 의해 초기화되지 않은 오류를 처리하기 위해 시도/캐치/마지막으로 수행할 수 있습니다.저는 이것이 이 답변에 대한 일부 사람들의 해결책으로 이해하기 어렵다고 생각합니다.그것이 제가 제 것을 추가하는 대부분의 이유입니다.C#에서 게터 및 세터 기능을 사용하면 이 기본 생성자의 넌센스를 무시하고 초기화하지 않은 것을 시도해 볼 수 있습니다.나에게는 이것이 잘 작동하고, 다른 사람에게는 if 문을 추가할 수 있습니다.따라서 Numerator/분모자 설정을 원하는 경우 이 코드가 도움이 될 수 있습니다.이 솔루션은 보기에 좋지 않고 효율성 측면에서는 훨씬 더 나쁘게 작동할 수 있지만, 이전 버전의 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;
}
}
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
'programing' 카테고리의 다른 글
| iOS Simulator에서 네트워크 호출을 모니터링하는 방법 (0) | 2023.05.14 |
|---|---|
| Postgres 스크립트에서 오류가 발생할 경우 어떻게 중지할 수 있습니까? (0) | 2023.05.14 |
| Git Cherry-pick vs 병합 워크플로우 (0) | 2023.05.14 |
| "instantiated"와 "initialized"의 차이는 무엇입니까? (0) | 2023.05.14 |
| git 디렉터리에 없는 동안 git pull (0) | 2023.05.14 |