首页 > 解决方案 > 如何为属性定义两种可能的数据类型

问题描述

我正在尝试在 C# 中为 2 个类定义运算符重载:FractionBasicFraction. 在外部,它们的行为相同,但在内部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 种数据类型?

笔记:

编辑:目前,PrimeFactorization存储因素及其力量Dictionary<int, BasicFraction>(我会使用Fraction,但它会导致递归;这基本上是唯一的用法BasicFraction)。

标签: c#oopinterface

解决方案


您想到的称为有区别的联合,在 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()));
        }
    }
}

请注意,声明素因子列表 aIList<int>允许您使用int[]或初始化分数List<int>

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


推荐阅读