我正在开发一个简单的数学库用于教育目的,我已经实现了代表Rational Number的struct
。显示结构核心字段的非常基本的代码是:
public struct RationalNumber
{
private readonly long numerator;
private readonly long denominator;
private bool isDefinitelyCoprime;
private static RationalNumber zero = 0;
public RationalNumber(long numerator, long denominator)
{
this.numerator = numerator;
this.denominator = denominator;
this.isDefinitelyCoprime = false;
}
...
}
目前我正在实施一个RationalMatrix
,正如您可能猜到的那样,它将由RationalNumber
个类型元素组成。
我正在创建静态构建器的有用矩阵是Identity矩阵。代码如下:
public static RationalMatrix GetIdentityMatrix(int dimension)
{
RationalNumber[,] values = new RationalNumber[dimension, dimension];
for (int i = 0; i < dimension; i++)
values[i, i] = 1;
return new RationalMatrix(values);
}
问题是这不起作用,因为我的RationalNumber
的默认值不是0/1
而是0/0
,这是一种特殊的值(Indeterminate form)。
显然,一个解决方案很简单,只需将方法更改为:
public static RationalMatrix GetIdentityMatrix(int dimension)
{
RationalNumber[,] values = new RationalNumber[dimension, dimension];
for (int i = 0; i < dimension; i++)
for (int j = i+1 ; j < dimension; j++)
{
values[i, i] = 1;
values[i, j] = RationalNumber.Zero;
values[j, i] = RationalNumber.Zero;
}
return new RationalMatrix(values);
}
但是这似乎是浪费精力,因为我基本上是将整个数组的值初始化两次。我认为以某种方式使RationalNumber
的默认值等于0/1
会更优雅。如果RationalNumber是class
,这很容易做到,但是当它是struct
时我想不出办法。我错过了一些明显的东西,或者没有办法避免将0/0
作为我的默认值吗?
我想指出的是,我并不关心代码性能(如果这是我的瓶颈,那么我已经远远超过了我的目标)。我只是想知道是否有一些构造(我不知道)允许你在struct
中强加任意默认值。
编辑:错别字
编辑2 :扩大问题范围
好的,根据我有限的C#知识,似乎没有办法根据我得到的输入和我自己的结论强加struct
中的任意默认值。
有人能给我一个线索,说明为什么结构必须这样做?是出于某种原因还是以这种方式实现的,因为没有人想过指定定义默认值的选项?
答案 0 :(得分:5)
如果您不必区分不确定的0/0和其他0 / N值,则可以将所有0 / N视为零。也就是说,所有零都是有意义的(0/2等于0/1),并且所有除以零都是相等的,因此1/0 == 2/0。
public struct RationalNumber : IEquatable<RationalNumber>
{
private readonly long numerator;
private readonly long denominator;
public RationalNumber(long numerator, long denominator)
{
this.numerator = numerator;
this.denominator = denominator;
}
public bool IsZero
{
get { return numerator == 0; }
}
public bool IsInvalid
{
get { return denominator == 0 && numerator != 0; }
}
public bool Equals(RationalNumber r)
{
if (r.IsZero && IsZero)
return true;
if (r.IsInvalid && IsInvalid)
return true;
return denominator == r.denominator && numerator == r.numerator;
}
public bool Equals(object o)
{
if (!(o is RationalNumber))
return false;
return Equals((RationalNumber)o);
}
public int GetHashCode()
{
if (IsZero)
return 0;
if (IsInvalid)
return Int32.MinValue;
return ((float)numerator/denominator).GetHashCode();
}
}
答案 1 :(得分:1)
您不能拥有指定默认值的无参数构造函数。技术原因是您的struct
是System.ValueType
的子类,而System.ValueType()
是protected
,因此无法覆盖。
你能得到的最接近的可能是David Hefferman的解决方案:
/// <summary>
/// The denominator is stored in this altered form, because all struct fields initialize to 0 - and we want newly created RationalNumbers to be 0/1 more often than 0/0.
/// </summary>
private int _denominatorMinusOne;
public int Denominator
{
get { return _denominatorMinusOne + 1; }
set { _denominatorMinusOne = value -1; }
}
然后您可以正常引用代码中的Denominator
,并且特殊存储格式将是透明的 - 您只能通过查看字段声明或通过仔细检查默认构造函数行为来判断。 / p>
你可以做一些事情,比如用参数调用一个构造函数,或者创建一个RationalNumberFactory
类来为你生成零 - 但是这些都不会解决你在循环遍历矩阵的每个元素而不仅仅是对角线,因为你不能指定数组初始值设定项将使用的构造函数。
事实上,new RationalNumber[100][100]
约定不仅仅是编码速记,而且运行速度比调用构造函数10,000次要快。这是System.ValueType()
首先成为protected
的部分原因。请参阅:Why can't I define a default constructor for a struct in .NET?
循环遍历矩阵的每个元素提供了清晰度的优势,但使用“怪异”减1解决方案不仅减少了运行代码的数量,而且还提高了性能。所以你可以把它作为一个有利的论据。
答案 2 :(得分:0)
为结构提供默认构造函数会很好:
public RationalNumber()
{
this.numerator = 0;
this.denominator = 1;
this.isDefinitelyCoprime = false;
}
然而,这是不允许的。你也没有实例初始化者。
答案只是你必须接受内部字段必须初始化为零,但这并不意味着必须遵循这种行为。
public struct Rational
{
private int _numerator;
private int _denominator;
public Rational(int numerator, int denominator)
{
// Check denominator is positive.
if(denominator < 0){
denominator *= -1;
numerator *= -1;
}
_numerator = numerator;
_denominator = denominator== 0? -1:
denominator;
}
public int Numerator
{
get { return _numerator; }
}
public int Denominator
{
get { return
_denominator == 0?1:
_denominator == -1?0:
_denominator; }
}
}
(注意:实际上我很惊讶地发现你不能在结构中使用静态初始化器!)
答案 3 :(得分:0)
在可能的情况下设计结构是好的,这样字段值的任何组合都将定义语义。如果没有这样做,那么结构通常无法通过不正确的线程代码来防止构造格式错误的实例,并且这样的实例会导致正确线程化的代码中的不正确行为。例如,如果有理类型的存储位置具有被识别为绝对互质的分子和分母值,并且所述位置被复制到一个线程中而其值在另一个线程中被更改,则执行复制的线程可以接收实例分子和分母不是互质的,但国旗说它们是。收到该实例的其他代码可能由于不变的不变而以奇怪和奇怪的方式失败;这种故障可能发生在离创建破坏实例的非线程安全代码很远的地方。
这种情况可以通过使用不可变类对象来保存有理数,并且具有包含对这样的对象的私有引用的有理数值类型来解决。包装类型在其私有引用为null时使用默认实例,在不包含时包含实例。如果私有引用是抽象类型并且有几个派生类型满足不同的标准,则这种方法可以提供一些潜在的效率改进。例如,可以有一个派生RationalSmallInteger
,其唯一字段为Int32
,而RationalLongInteger
的唯一字段为Int64
(Denominator
属性这两种类型总是会返回1)。人们可能有这样的类型,其中分母是非零的,但被证实是与分子或类型相互作用的类型;后一种类型可以保持对分子和分母保证是互质的实例的初始空引用。在以下情况下,此类行为可以提高效率:
RationalNumber r1 = new RationalNumber(4,6);
RationalNumber r2 = r1;
RationalNumber r3 = r1.ReducedForm();
RationalNumber r4 = r2.ReducedForm();
第一个语句将r1的private字段设置为引用RationalNumber.Int32by32Nonreduced
实例。第二个将r2的私有字段设置为指向同一个实例。第三个语句将生成一个新的Int32by32Reduced
实例,并在前一个Int32by32Nonreduced
实例中以及r3的私有字段中存储对该实例的引用。第四个将从前Int32by32Reduced
获取上述参考,并将其存储到r4的私有字段中。请注意,只需要一次减速操作。相反,如果RationalNumber
是一个在内部保持其值的结构,则第四个语句将无法重新使用第三个执行的减少的结果。