如何为 属性 定义两种可能的数据类型
How to define two possible data type for property
我正在尝试在 C# 中为 2 类:Fraction
和 BasicFraction
定义运算符重载。
在外部,它们的行为相同,但在内部 Fraction
使用 PrimeFactorization
(将数字存储为素数列表)而 BasicFraction
使用 int
存储分母和分母.
他们会遵循这个接口(如果我使用 JSDoc 类型定义):
public interface IFraction
{
public (int|Primefactoriztion) a; // nominator
public (int|Primefactoriztion) b; // denominator
}
但是因为我不能这样做,有没有一种简单的方法可以互换使用 Fraction 和 BasicFraction?
我可以定义所有运算符重载以评估 int
和 PrimeFactorization
之间的交互。但是,我不知道如何告诉 C# 接受要传递给方法的两者。
Tl;dr:如何使一个 属性 允许 2 种数据类型?
备注:
- 我是高级 C# 的新手(我知道静态类型和基本的 OOP,但仅此而已)。我来自 JS 和 Python 背景。
- 我知道它们可能是具有此功能的好库。我写这篇文章是为了学习 C#,而不是为了生产。
编辑:
目前,PrimeFactorization
将因子及其幂存储在 Dictionary<int, BasicFraction>
中(我会使用 Fraction
,但它会导致递归;这基本上是 BasicFraction
的唯一用法)。
你想要一个可区分的联合类型。 C# 不支持它们作为第一个 class 语言功能,但您可以使用 OneOf 对其进行破解:https://github.com/mcintyre321/OneOf
所以你最终会得到:
public interface IFraction
{
public OneOf< int, Primefactoriztion > a; // nominator
public OneOf< int, Primefactoriztion > b; // denominator
}
但它仍然是一种代码味道。我认为你需要重新考虑你的整个方法。此外,使用接口来表示 values 可能不是一个好主意(由于所有堆分配),特别是因为您的 a
和 b
成员出现可变。
一个更好的主意是更改您的 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
}
您想到的称为 discriminated union 并且在 C# 中不作为语言功能提供。
我会使用混合方法,其中分数将包含作为 int
的分母和分母以及作为只读属性的质因数列表。您可以通过两个构造函数使用整数或素因子列表初始化分数。将延迟计算缺失的条目以最小化计算开销。
将属性设为只读可提高代码的稳健性。特别是如果您使用的是结构。请参阅 Mutating readonly structs(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# 中为 2 类:Fraction
和 BasicFraction
定义运算符重载。
在外部,它们的行为相同,但在内部 Fraction
使用 PrimeFactorization
(将数字存储为素数列表)而 BasicFraction
使用 int
存储分母和分母.
他们会遵循这个接口(如果我使用 JSDoc 类型定义):
public interface IFraction
{
public (int|Primefactoriztion) a; // nominator
public (int|Primefactoriztion) b; // denominator
}
但是因为我不能这样做,有没有一种简单的方法可以互换使用 Fraction 和 BasicFraction?
我可以定义所有运算符重载以评估 int
和 PrimeFactorization
之间的交互。但是,我不知道如何告诉 C# 接受要传递给方法的两者。
Tl;dr:如何使一个 属性 允许 2 种数据类型?
备注:
- 我是高级 C# 的新手(我知道静态类型和基本的 OOP,但仅此而已)。我来自 JS 和 Python 背景。
- 我知道它们可能是具有此功能的好库。我写这篇文章是为了学习 C#,而不是为了生产。
编辑:
目前,PrimeFactorization
将因子及其幂存储在 Dictionary<int, BasicFraction>
中(我会使用 Fraction
,但它会导致递归;这基本上是 BasicFraction
的唯一用法)。
你想要一个可区分的联合类型。 C# 不支持它们作为第一个 class 语言功能,但您可以使用 OneOf 对其进行破解:https://github.com/mcintyre321/OneOf
所以你最终会得到:
public interface IFraction
{
public OneOf< int, Primefactoriztion > a; // nominator
public OneOf< int, Primefactoriztion > b; // denominator
}
但它仍然是一种代码味道。我认为你需要重新考虑你的整个方法。此外,使用接口来表示 values 可能不是一个好主意(由于所有堆分配),特别是因为您的 a
和 b
成员出现可变。
一个更好的主意是更改您的 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
}
您想到的称为 discriminated union 并且在 C# 中不作为语言功能提供。
我会使用混合方法,其中分数将包含作为 int
的分母和分母以及作为只读属性的质因数列表。您可以通过两个构造函数使用整数或素因子列表初始化分数。将延迟计算缺失的条目以最小化计算开销。
将属性设为只读可提高代码的稳健性。特别是如果您使用的是结构。请参阅 Mutating readonly structs(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>
初始化分数。
但是质因数是否真的需要存储是值得考虑的。一些计算需要的时候计算它们还不够吗?