如何为属性定义两种可能的数据类型



我试图在c#中定义两个类的操作符重载:FractionBasicFraction。在外部,它们的行为相同,但内部Fraction使用PrimeFactorization(将数字存储为质因数列表),BasicFraction使用int存储主分母。

它们将遵循以下接口(如果我使用JSDoc类型定义):

public interface IFraction
{
public (int|Primefactoriztion) a; // nominator
public (int|Primefactoriztion) b; // denominator
}

但是既然我不能这样做,是否有一种简单的方法可以互换使用Fraction和BasicFraction ?
我可以定义所有运算符重载来计算intPrimeFactorization之间的交互。然而,我不知道如何告诉c#接受这两个传递给方法。

Tl;dr:如何使一个属性允许2个数据类型?

指出:

  • 我是高级c#的新手(我知道静态类型和基本的OOP,但仅此而已)。我来自JS和Python背景。
  • 我知道它们可能是具有此功能的好库。我写这篇文章是为了学习c#,而不是为了生产。

编辑:目前,PrimeFactorization将因子及其幂函数存储在Dictionary<int, BasicFraction>中(我想使用Fraction,但它会导致递归;这基本上是BasicFraction的唯一用法。

你所想到的被称为区分联合,在c#中不作为语言特性可用。

我将使用混合方法,其中分数将包含主因子和分母作为int加上素数因子列表作为只读属性。可以通过两个构造函数用整型数或素数因子列表初始化分数。缺少的条目将被惰性地计算,以最小化计算开销。

使属性只读可以增加代码的健壮性。特别是在使用结构体时。参见变异只读结构体(Eric Lippert的博客:美妙的编码冒险)。但是请注意,由于惰性求值,该结构仍然是可变的。

public struct Fraction
{
public Fraction(int nominator, int denominator)
{
_nominator = nominator;
_denominator = denominator;
_nominatorPrimeFactors = null;
_denominatorPrimeFactors = null;
}
public Fraction(IList<int> nominatorPrimeFactors, IList<int> denominatorPrimeFactors)
{
if (nominatorPrimeFactors == null || nominatorPrimeFactors.Count == 0) {
throw new ArgumentNullException(
$"{nameof(nominatorPrimeFactors)} must be a non-null, non-empty list");
}
if (denominatorPrimeFactors == null || denominatorPrimeFactors.Count == 0) {
throw new ArgumentNullException(
$"{nameof(denominatorPrimeFactors)} must be a non-null, non-empty list");
}
_nominator = null;
_denominator = null;
_nominatorPrimeFactors = nominatorPrimeFactors;
_denominatorPrimeFactors = denominatorPrimeFactors;
}
private int? _nominator;
public int Nominator
{
get {
if (_nominator == null) {
_nominator = _nominatorPrimeFactors.Aggregate(1, (x, y) => x * y);
}
return _nominator.Value;
}
}
private int? _denominator;
public int Denominator
{
get {
if (_denominator == null) {
_denominator = _denominatorPrimeFactors.Aggregate(1, (x, y) => x * y);
}
return _denominator.Value;
}
}
private IList<int> _nominatorPrimeFactors;
public IList<int> NominatorPrimeFactors
{
get {
if (_nominatorPrimeFactors == null) {
_nominatorPrimeFactors = Factorize(Nominator);
}
return _nominatorPrimeFactors;
}
}
private IList<int> _denominatorPrimeFactors;
public IList<int> DenominatorPrimeFactors
{
get {
if (_denominatorPrimeFactors == null) {
_denominatorPrimeFactors = Factorize(Denominator);
}
return _denominatorPrimeFactors;
}
}
private static List<int> Factorize(int number)
{
var result = new List<int>();
while (number % 2 == 0) {
result.Add(2);
number /= 2;
}
int factor = 3;
while (factor * factor <= number) {
if (number % factor == 0) {
result.Add(factor);
number /= factor;
} else {
factor += 2;
}
}
if (number > 1) result.Add(number);
return result;
}
public override string ToString()
{
if (_nominatorPrimeFactors == null && _denominatorPrimeFactors == null) {
return $"{_nominator}/{_denominator}";
}
string npf = ListToString(_nominatorPrimeFactors);
string dpf = ListToString(_denominatorPrimeFactors);
if (_nominator == null && _denominator == null) {
return $"({npf}) / ({dpf})";
}
return $"{_nominator}/{_denominator}, ({npf}) / ({dpf})";

static string ListToString(IList<int> primeFactors)
{
if (primeFactors == null) {
return null;
}
return String.Join(" * ", primeFactors.Select(i => i.ToString()));
}
}
}

请注意,声明质数因子列表的IList<int>允许您使用int[]List<int>初始化分数。

但是值得考虑的是质数因子是否真的需要存储。当需要计算时,计算它们还不够吗?

你想要一个判别联合类型。c#本身不支持它们作为一级语言特性,但您可以使用OneOf: https://github.com/mcintyre321/OneOf

破解它。所以你最终会得到:

public interface IFraction
{
public OneOf< int, Primefactoriztion > a; // nominator
public OneOf< int, Primefactoriztion > b; // denominator
}

但它仍然是一个代码气味。我觉得你需要重新考虑一下你的整个方法。此外,使用接口来表示可能不是一个好主意(由于所有的堆分配),特别是因为您的ab成员似乎是可变的。


一个更好的主意是改变你的Primefactoriztion类型(我希望并假设是struct,而不是class),以支持从int隐式转换和一些成员显式转换到int,如果操作不安全(例如,如果值不是整数):

struct Primefactoriztion
{
public static implicit operator int(Primefactoriztion self)
{
return ...
}
}

这样你就可以安全地去掉OneOf<int,只使用Primefactoriztion:

public interface IFraction
{
public Primefactoriztion a; // nominator
public Primefactoriztion b; // denominator
}

最新更新