In .NET, a value type (C# struct
) can\'t have a constructor with no parameters. According to this post this is mandated by the CLI specification. What happens i
What I use is the null-coalescing operator (??) combined with a backing field like this:
public struct SomeStruct {
private SomeRefType m_MyRefVariableBackingField;
public SomeRefType MyRefVariable {
get { return m_MyRefVariableBackingField ?? (m_MyRefVariableBackingField = new SomeRefType()); }
}
}
Hope this helps ;)
Note: the null coalescing assignment is currently a feature proposal for C# 8.0.
I haven't seen equivalent to late solution I'm going to give, so here it is.
use offsets to move values from default 0 into any value you like. here properties must be used instead of directly accessing fields. (maybe with possible c#7 feature you better define property scoped fields so they remain protected from being directly accessed in code.)
This solution works for simple structs with only value types (no ref type or nullable struct).
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;
}
}
This is different than this answer, this approach is not especial casing but its using offset which will work for all ranges.
example with enums as field.
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;
}
}
As I said this trick may not work in all cases, even if struct has only value fields, only you know that if it works in your case or not. just examine. but you get the general idea.
public struct Rational
{
private long numerator;
private long denominator;
public Rational(long num = 0, long denom = 1) // This is allowed!!!
{
numerator = num;
denominator = denom;
}
}
Here's my solution to the no default constructor dilemma. I know this is a late solution, but I think it's worth noting this is a solution.
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 );
}
}
ignoring the fact I have a static struct called null, (Note: This is for all positive quadrant only), using get;set; in C#, you can have a try/catch/finally, for dealing with the errors where a particular data type is not initialized by the default constructor Point2D(). I guess this is elusive as a solution to some people on this answer. Thats mostly why i'm adding mine. Using the getter and setter functionality in C# will allow you to bypass this default constructor non-sense and put a try catch around what you dont have initialized. For me this works fine, for someone else you might want to add some if statements. So, In the case where you would want a Numerator/Denominator setup, this code might help. I'd just like to reiterate that this solution does not look nice, probably works even worse from an efficiency standpoint, but, for someone coming from an older version of C#, using array data types gives you this functionality. If you just want something that works, try this:
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;
}
}