【问题标题】:Too many generics in C#C# 中的泛型太多
【发布时间】:2021-03-25 09:02:23
【问题描述】:

我正在编写一个数学优化算法,并面临大量泛型参数的问题。有解决方案、问题、运算符和算法。每个都有自己的接口结构,因此可以用新的类进行扩展。使用在 Main 函数中创建变异算子 NonUniform 的示例,我展示了其创建的复杂性。如果您很少创建此类对象,这不是问题,但是有许多类似的运算符,并且对于每个,您需要列出所有通用参数,并且它们也会重复。我试图将这些参数传递给方法,但是在继承期间,无法指定更严格的限制。如何摆脱这么多仿制药?也许值得修改架构,或者语言的某些机制我不知道。

#region Algorithms

interface IAlgorithm<out TProblem, out TSolution>
    where TProblem : IProblem<TSolution>
    where TSolution : ISolution
{
    Random Random { get; }
    TProblem Problem { get; }
    TSolution Best { get; }
}

interface IIterativeAlgorithm<out TProblem, out TSolution>
    : IAlgorithm<TProblem, TSolution>
    where TProblem : IProblem<TSolution>
    where TSolution : ISolution
{
    int CurrentIteration { get; }
}

class IterativeAlgorithm<TProblem, TSolution>
    : IIterativeAlgorithm<TProblem, TSolution>
    where TProblem : IProblem<TSolution>
    where TSolution : ISolution
{
    public Random Random { get; }
    public TProblem Problem { get; }
    public TSolution Best { get; }
    public int CurrentIteration { get; }
}

#endregion

#region Operator

interface IOperator<in TAlgorithm, TProblem, TSolution>
    where TAlgorithm : IAlgorithm<TProblem, TSolution>
    where TProblem : IProblem<TSolution>
    where TSolution : ISolution
{
    public void SetAlgorithm(TAlgorithm algorithm);
}

interface ICoordinateMutator<in TAlgorithm, TFunction, TPoint>
    : IOperator<TAlgorithm, TFunction, TPoint>
    where TAlgorithm : IAlgorithm<TFunction, TPoint>
    where TFunction : IFunction<TPoint>
    where TPoint : IPoint
{
    void MutateCoordinates(TPoint point);
}

abstract class CoordinateMutator<TAlgorithm, TFunction, TPoint>
    : ICoordinateMutator<TAlgorithm, TFunction, TPoint>
    where TAlgorithm : IAlgorithm<TFunction, TPoint>
    where TFunction : IFunction<TPoint>
    where TPoint : IPoint
{
    protected Func<Random> GetRandom { get; set; }

    public CoordinateMutator()
    {
        GetRandom = null;
    }

    public virtual void SetAlgorithm(TAlgorithm algorithm)
    {
        GetRandom = () => algorithm.Random;
    }

    public abstract void MutateCoordinates(TPoint point);
}

class NonUniform<TIterativeAlgorithm, TFunction, TPoint> :
    CoordinateMutator<TIterativeAlgorithm, TFunction, TPoint>
    where TIterativeAlgorithm : IIterativeAlgorithm<TFunction, TPoint>
    where TFunction : IFunction<TPoint>
    where TPoint : IPoint
{
    private Func<int> GetCurrentIteration { get; set; }
    private Func<double[]> GetLowerSearchBorder { get; set; }
    private Func<double[]> GetUpperSearchBorder { get; set; }

    public override void SetAlgorithm(TIterativeAlgorithm algorithm)
    {
        base.SetAlgorithm(algorithm);
        
        GetCurrentIteration = () => algorithm.CurrentIteration;
        GetLowerSearchBorder = () => algorithm.Problem.LowerSearchBorders;
        GetUpperSearchBorder = () => algorithm.Problem.UpperSearchBorders;
    }

    public override void MutateCoordinates(TPoint point)
    {
        Random random = GetRandom();
        double currentIteration = GetCurrentIteration();
        double[] lowerSearchBorders = GetLowerSearchBorder();
        double[] upperSearchBorders = GetUpperSearchBorder();

        throw new NotImplementedException("TODO");
    }
}

#endregion

#region Problems

interface IProblem<in TSolution>
    where TSolution : ISolution
{
}

interface IFunction<in TPoint>
    : IProblem<TPoint>
    where TPoint : IPoint
{
    double[] LowerSearchBorders { get; }
    double[] UpperSearchBorders { get; }
}

abstract class Function<TPoint>
    : IFunction<TPoint>
    where TPoint : IPoint
{
    public double[] LowerSearchBorders { get; }
    public double[] UpperSearchBorders { get; }
}

class Sphere<TPoint>
    : Function<TPoint>
    where TPoint : IPoint
{
    public double GetValueOf(TPoint solution) => throw new NotImplementedException("TODO");
}

#endregion

#region Solutions

interface ISolution
{
}

interface IPoint : ISolution
{
}

class Point : IPoint
{
}

#endregion

private static void Main()
{
    var cm = new NonUniform<IterativeAlgorithm<Function<Point>, Point>, Function<Point>, Point>();
}

【问题讨论】:

  • 如果你觉得很难理解,那么我建议你将每个类分成一个单独的文件,一次专注于一个,让接口/泛型正确,并确保你写得好单元测试。您正在有效地创建一个库,而当您有很多事情要做时,这似乎令人生畏。
  • 此代码专门呈现在一张纸上,以便您了解这些类是如何相互关联的。我还没有遇到扩展功能/架构和类之间交互的问题。问题是类,例如 NonUniform,由于大量重复的泛型,不方便创建。
  • 所有泛型的目的是什么?是否只允许 Copy 方法返回正确类型的对象?您是否考虑过其他解决方案?喜欢尽可能使用不变性,从而避免需要副本。或者只是返回一个对象,并让调用者在需要时转换为正确的类型。
  • 泛型的目标是类型安全。也就是说,使用适当的类。例如,在 NonUniform 中,运算符应使用 IIterativeAlgorithm,而此类型的其他运算符可以简单地使用 IAlgorithm。我将从提供的代码中删除 ICopy 接口,因为它会分散问题的注意力。在 Main 方法中创建对象时,使用不变性无助于摆脱重复的泛型参数。
  • 不需要泛型来确保类型安全,常规类型就足够了。泛型主要用于 1. 各种类型的容器,2. 优化,即编译器为每种特定类型生成代码,这可以改善内联,但也会导致代码体积膨胀。

标签: c# generics inheritance architecture


【解决方案1】:

我认为泛型过多的主要原因之一来自ICopy&lt;T&gt; 接口。

您可以使它成为非泛型并让 Copy 方法返回一个对象。或者改用 IClonable 接口。 它会减少其他接口和类的泛型参数,但每次调用 Copy 方法时都需要对其结果进行强制转换。

另一种选择是将 C# 9 的 Covariant return 功能与 .NET 5 结合使用

当接口继承 ICopy 时,它不必是通用的

    interface ISolution : ICopy<ISolution>
    {
    }
    interface IPoint : ISolution
    {
    }

你需要声明一个实现接口的抽象类:

    abstract class PointBase : IPoint
    {
        public abstract ISolution Copy();
    }

然后您可以使用协变返回来实现具有更具体类型的 Copy 方法

    class Point : PointBase
    {
        public override Point Copy() => new Point();
    }

【讨论】:

  • 是的,这有助于清理代码。可以在更具体的接口/类中添加 Copy 方法。但同样,要创建非统一运算符,您需要指定 3 个具有限制的泛型参数,并且复制方法不会影响这一点。我将修改提供的代码并删除Copy接口。
【解决方案2】:

据我所知,不需要泛型。例如

interface ISolution
{
}

这是干什么用的?它没有属性或方法,所以我无法对解决方案做任何事情。唯一的用法似乎是作为标记,例如依赖注入/反射,但您似乎没有以这种方式使用它。

另一个例子是方法

public override void MutateCoordinates(TPoint point)
{
    Random random = GetRandom();
    double currentIteration = GetCurrentIteration();
    double[] lowerSearchBorders = GetLowerSearchBorder();
    double[] upperSearchBorders = GetUpperSearchBorder();

    throw new NotImplementedException("TODO");
}

我们知道该点是IPoint,即ISolution。但那又怎样?我们不能将点用于任何事情,因为它没有属性或方法,“TODO”很方便地省略了一些东西。我们可以将float X {get;} float Y {get;} 之类的属性添加到接口中以允许实际使用,但是我们也可以使用方法签名

public override void MutateCoordinates(IPoint point)

使用泛型的一个可能原因是它可能允许更多优化,但代价是更大的代码大小。如果您对点数百万次调用方法,这可能与点之类的东西相关,但可能与IAlgorithm 等更高级别的概念无关。分析和/或基准测试应该显示这是否相关。标准建议是从最简单的解决方案开始。

我会考虑修改您的结构并删除几乎所有使用常规类型的泛型。为这样的领域创建一个好的描述可能非常困难,如果没有更多信息,很难提供好的建议。泛型可能是某种建模的最佳解决方案。但是,如示例所示,用泛型类型替换所有类型可能不是可行的方法。

【讨论】:

  • ISolution 和 IPoint 接口具有属性和方法。我没有指定它们,只显示了继承层次结构。方法签名不能以您显示的方式更改,因为这只是一个运算符,它将嵌入到其他将使用从 IPoint 继承的其他类型的链中。
  • @Boris Melnikov 在MutateCoordinates 中对TPoint 的唯一限制是它是IPoint。因此它不能调用任何具有更严格的通用约束的方法。因此,它可以用接口本身替换。如果它需要返回Something&lt;T&gt;,它可能会有所不同,但它不需要。
猜你喜欢
  • 2020-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-14
  • 1970-01-01
  • 2012-01-20
  • 2018-06-07
相关资源
最近更新 更多