在. net中,值类型(c# struct)不能有不带形参的构造函数。根据这篇文章,这是CLI规范要求的。实际情况是,对于每一个值类型,都会创建一个默认构造函数(由编译器创建?),该构造函数将所有成员初始化为零(或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定义的默认构造函数?


琼恩·斯基特回答说:

用你的例子来说,如果有人这么做了,你希望发生什么: Rational[]分数= new Rational[1000]; 它应该遍历构造函数1000次吗?

当然应该,这就是为什么我首先写默认构造函数的原因。当没有明确定义默认构造函数时,CLR应该使用默认的归零构造函数;这样你只需要为你所使用的付费。然后,如果我想要一个包含1000个非默认Rational的容器(并且想要优化掉这1000个结构),我将使用List<Rational>而不是数组。

在我看来,这个原因还不足以阻止定义默认构造函数。


当前回答

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

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

其他回答

我找到了一个简单的解决方法:

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();

struct是一种值类型,值类型在声明后必须具有默认值。

MyClass m;
MyStruct m2;

如果您像上面那样声明了两个字段而没有实例化它们,那么就会中断调试器,m将为null,而m2则不是。鉴于此,无参数构造函数将没有意义,事实上,结构上的所有构造函数都是赋值,只是声明了它本身就已经存在了。实际上,m2可以在上面的例子中非常愉快地使用,并调用它的方法(如果有的话),并操纵它的字段和属性!

你可以创建一个静态属性,初始化并返回一个默认的“有理数”:

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

像这样使用它:

var rat = Rational.One;

下面是我对没有默认构造函数困境的解决方案。我知道这是一个很晚的解决方案,但我认为值得注意的是,这是一个解决方案。

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;
    }
}

短的说明:

在c++中,结构体和类只是一枚硬币的两面。唯一真正的区别是,一个默认是公开的,另一个是私有的。

在. net中,结构体和类之间有更大的区别。主要是struct提供值类型语义,而class提供引用类型语义。当您开始考虑此更改的含义时,其他更改也开始变得更有意义,包括您描述的构造函数行为。