在C#中优化初始化类实例数组

Mac*_*day 35 c# arrays initialization

假设我有这样一个类:

public class Fraction
{
   int numerator;
   int denominator;

   public Fraction(int n, int d)
   {
      // set the member variables
   }

   // And then a bunch of other methods
}
Run Code Online (Sandbox Code Playgroud)

我想以一种很好的方式初始化它们的数组,这篇文章是一个很容易出错或语法上很麻烦的方法列表.

当然数组构造函数会很好,但是没有这样的东西:

public Fraction[](params int[] numbers)
Run Code Online (Sandbox Code Playgroud)

所以我被迫使用像这样的方法

public static Fraction[] CreateArray(params int[] numbers)
{
    // Make an array and pull pairs of numbers for constructor calls
}
Run Code Online (Sandbox Code Playgroud)

这是相对笨重的,但我没有看到解决方法.

这两种形式都容易出错,因为用户可能错误地传递了奇数个参数,可能是因为他/她跳过了一个值,这会让这个函数不知所措地想知道用户究竟想要什么.它可能会抛出异常,但用户需要尝试/ catch.如果可能的话,我宁愿不对用户施加压力.所以让我们强制执行配对.

public static Fraction[] CreateArray(params int[2][] pairs)
Run Code Online (Sandbox Code Playgroud)

但你不能以一种很好的方式调用这个CreateArray,比如

Fraction.CreateArray({0,1}, {1,2}, {1,3}, {1,7}, {1,42});
Run Code Online (Sandbox Code Playgroud)

你甚至做不到

public static Fraction[] CreateArray(int[2][] pairs)
// Then later...
int[2][] = {{0,1}, {1,2}, {1,3}, {1,7}, {1,42}};
Fraction.CreateArray(numDenArray);
Run Code Online (Sandbox Code Playgroud)

请注意,这在C++中可以正常工作(我很确定).

你被迫做了以下其中一种,这是令人憎恶的.语法很糟糕,当所有元素具有相同的长度时,使用锯齿状数组似乎很尴尬.

int[2][] fracArray = {new int[2]{0,1}, /*etc*/);
Fraction.CreateArray(fracArray);
// OR
Fraction.CreateArray(new int[2]{0,1}, /*etc*/);
Run Code Online (Sandbox Code Playgroud)

类似地,Python风格的元组是非法的,C#版本是icky:

Fraction.CreateArray(new Tuple<int,int>(0,1), /*etc*/);
Run Code Online (Sandbox Code Playgroud)

纯2D阵列的使用可能采用以下形式,但这是非法的,我确信没有合法的方式来表达它:

public static Fraction[] CreateArray(int[2,] twoByXArray)
// Then later...
Fraction[] fracArray = 
    Fraction.CreateArray(new int[2,4]{{0,1}, {1,2}, {1,3}, {1,6}});
Run Code Online (Sandbox Code Playgroud)

这不会强制执行:

public static Fraction[] CreateArray(int[,] twoByXArray)
Run Code Online (Sandbox Code Playgroud)

好的,怎么样

public static Fraction[] CreateArray(int[] numerators, int[] denominators)
Run Code Online (Sandbox Code Playgroud)

但是这两个阵列可能有不同的长度.C++允许

public static Fraction[] CreateArray<int N>(int[N] numerators, int[N] denominators)
Run Code Online (Sandbox Code Playgroud)

但是,这不是C++,是吗?

这种事情是非法的:

public static implicit operator Fraction[](params int[2][] pairs)
Run Code Online (Sandbox Code Playgroud)

无论如何,再次因为令人讨厌的语法:

Fraction[] fracArray = new Fraction[](new int[2]{0,1}, /*etc*/ );
Run Code Online (Sandbox Code Playgroud)

这可能很好:

public static implicit operator Fraction(string s)
{
    // Parse the string into numerator and denominator with
    // delimiter '/'
}
Run Code Online (Sandbox Code Playgroud)

那你可以做

string[] fracStrings = new string[] {"0/1", /*etc*/};
Fraction[] fracArray = new Fraction[fracStrings.Length];
int index = 0;
foreach (string fracString in fracStrings) {
    fracArray[index] = fracStrings[index];
}
Run Code Online (Sandbox Code Playgroud)

我不喜欢这种方法有五个原因.一,隐式转换不可避免地实例化一个新对象,但我们已经有一个非常好的对象,即我们试图初始化的对象.二,阅读可能令人困惑.三,它迫使你明确地做我想要封装的东西.四,它为不良格式留下了空间.五,它涉及一次性解析字符串文字,这更像是一个实用的笑话,而不是良好的编程风格.

以下还需要浪费实例化:

var fracArray = Array.ConvertAll(numDenArray, item => (Fraction)item);
Run Code Online (Sandbox Code Playgroud)

除非使用那些可怕的锯齿状数组,否则以下对属性的使用会产生同样的问题:

public int[2] pair {
    set {
        numerator = value[0];
        denominator = value[1];
    }
}
// Then later...
var fracStrings = new int[2,4] {{0,1}, /*etc*/};
var fracArray = new Fraction[fracStrings.Length];
int index = 0;
foreach (int[2,] fracString in fracStrings) {
    fracArray[index].pair = fracStrings[index];
}
Run Code Online (Sandbox Code Playgroud)

此变体不会强制执行对:

foreach (int[,] fracString in fracStrings) {
    fracArray[index].pair = fracStrings[index];
}
Run Code Online (Sandbox Code Playgroud)

同样,这种方法无论如何都很重要.

这些是我知道如何推导出来的所有想法.有一个很好的解决方案吗?

Iva*_*oev 39

我想不出一个优雅,同时内存高效的阵列解决方案.

但是使用C#6 集合初始化程序功能的列表(和类似)有一个优雅的解决方案:

public static class Extensions
{
    public static void Add(this ICollection<Fraction> target, int numerator, int denominator)
    {
        target.Add(new Fraction(numerator, denominator));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用该扩展方法,您可以轻松地初始化Fraction列表,例如:

var list = new List<Fraction> { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } };
Run Code Online (Sandbox Code Playgroud)

当然,虽然没有内存效率,但您可以使用它来初始化Fraction数组:

var array = new List<Fraction> { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } }.ToArray();
Run Code Online (Sandbox Code Playgroud)

甚至通过使用隐式数组转换运算符声明一个列表派生类来使其更简洁:

public class FractionList : List<Fraction>
{
    public static implicit operator Fraction[](FractionList x) => x?.ToArray();
}
Run Code Online (Sandbox Code Playgroud)

然后使用

Fraction[] array = new FractionList { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } };
Run Code Online (Sandbox Code Playgroud)

  • 好方案!如果我们在谈论代码中的手写元组,那么内存效率应该没关系!如果超过1000,它们应该在资源文件中! (5认同)
  • @GeorgeT:`添加`作为扩展方法,需要C#6.如果你坚持使用古老的版本,你总是可以创建一个实际的类,其中"添加"作为普通方法.这样你就不必使用可能具有多余功能的`List <T>`,你可以创建自己的类来实现`IEnumerable`并且有一个`Add`方法,这是使用的要求集合初始化器语法. (5认同)

And*_*ols 8

您可以使用流畅的界面创建一个分数数组构建器.它会导致类似的东西

public class FractionArrayBuilder
{
  private readonly List<Fraction> _fractions = new List<Fraction>();

  public FractionArrayBuilder Add(int n, int d)
  {
    _fractions.Add(new Fraction(n, d));
    return this;
  }

  public Fraction[] Build()
  {
    return _fractions.ToArray();
  }
}
Run Code Online (Sandbox Code Playgroud)

可以使用

var fractionArray = new FractionArrayBuilder()
  .Add(1,2)
  .Add(3,4)
  .Add(3,1)
  .Build();
Run Code Online (Sandbox Code Playgroud)

这是一个易于理解的陈述.

我做了一个小提琴演示.


Mat*_*son 7

对于您的特定示例,我能想到的最简洁的方法是为Fraction类编写一个隐式运算符:

public sealed class Fraction
{
    public Fraction(int n, int d)
    {
        Numerator   = n;
        Deniminator = d;
    }

    public int Numerator   { get; }
    public int Deniminator { get; }

    public static implicit operator Fraction(int[] data)
    {
        return new Fraction(data[0], data[1]);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样初始化它:

var fractions = new Fraction[]
{
    new [] {1, 2},
    new [] {3, 4},
    new [] {5, 6}
};
Run Code Online (Sandbox Code Playgroud)

不幸的是你仍然需要new []在每一行,所以我认为这不会比正常的数组初始化语法获得更多:

var fractions = new []
{
    new Fraction(1, 2),
    new Fraction(3, 4),
    new Fraction(5, 6)
};
Run Code Online (Sandbox Code Playgroud)

我想你可以写一个Func<>短名称的"本地" 来简化初始化:

Func<int, int, Fraction> f = (x, y) => new Fraction(x, y);

var fractions = new []
{
    f(1, 2),
    f(3, 4),
    f(5, 6)
};
Run Code Online (Sandbox Code Playgroud)

缺点是你需要Func<>在你想要初始化数组的地方添加额外的行(初始化a ) - 或者在类中使用私有静态方法 - 但是那个方法将在整个类的范围内,这不是如果它有一个单字母的名字,那就太理想了.

但是,这种方法的优点是它非常灵活.

我玩弄了调用内联函数的想法_,但我真的不确定...

Func<int, int, Fraction> _ = (x, y) => new Fraction(x, y);

var fractions = new []
{
    _(1, 2),
    _(3, 4),
    _(5, 6)
};
Run Code Online (Sandbox Code Playgroud)

  • 而不是本地的`Func`,我会考虑在某个类上使用`public static`方法,你可以通过`using static`访问它. (2认同)