在C#中对类实例数组的优雅初始化
假设我有这样的一个class级:
public class Fraction { int numerator; int denominator; public Fraction(int n, int d) { // set the member variables } // And then a bunch of other methods }
我想以一个很好的方式初始化它们的一个数组,这个post是一个容易出错或者在语法上很麻烦的大方法列表。
当然,一个数组构造函数会很好,但是没有这样的事情:
public Fraction[](params int[] numbers)
所以我不得不使用类似的方法
public static Fraction[] CreateArray(params int[] numbers) { // Make an array and pull pairs of numbers for constructor calls }
这是相对笨重的,但我没有看到它的方式。
这两种forms都是容易出错的,因为用户可能错误地传递了奇数参数,可能是因为他/她跳过一个值,这会使得该function挠挠脑袋,不知道用户真正想要什么。 它可能会抛出exception,但用户需要尝试/捕获。 如果可能的话,我宁愿不要强加给用户。 所以让我们执行配对。
public static Fraction[] CreateArray(params int[2][] pairs)
但是你不能以很好的方式调用这个CreateArray
Fraction.CreateArray({0,1}, {1,2}, {1,3}, {1,7}, {1,42});
你甚至不能这样做
public static Fraction[] CreateArray(int[2][] pairs) // Then later... int[2][] = {{0,1}, {1,2}, {1,3}, {1,7}, {1,42}}; Fraction.CreateArray(numDenArray);
请注意,这将在C ++中工作得很好(我很确定)。
你被迫做了以下其中一个,这是可恶的。 语法是可怕的,当所有的元素具有相同的长度时,使用锯齿状的数组看起来很尴尬。
int[2][] fracArray = {new int[2]{0,1}, /*etc*/); Fraction.CreateArray(fracArray); // OR Fraction.CreateArray(new int[2]{0,1}, /*etc*/);
同样,Python风格的元组是非法的,C#版本是icky:
Fraction.CreateArray(new Tuple<int,int>(0,1), /*etc*/);
纯二维数组的使用可能采取以下forms,但这是违法的,我相信没有合法的方式expression它:
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}});
这不强制配对:
public static Fraction[] CreateArray(int[,] twoByXArray)
好的,怎么样
public static Fraction[] CreateArray(int[] numerators, int[] denominators)
但是这两个数组可能有不同的长度。 C ++允许
public static Fraction[] CreateArray<int N>(int[N] numerators, int[N] denominators)
但是,这不是C ++,是吗?
这种事情是非法的:
public static implicit operator Fraction[](params int[2][] pairs)
而且无论如何也是不可行的,因为这个可恶的语法:
Fraction[] fracArray = new Fraction[](new int[2]{0,1}, /*etc*/ );
这可能是很好的:
public static implicit operator Fraction(string s) { // Parse the string into numerator and denominator with // delimiter '/' }
那你可以做
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]; }
我不喜欢这种方法有五个原因。 一个隐含的演员不可避免地实例化一个新的对象,但是我们已经有了一个完美的对象,也就是我们要初始化的对象。 二,阅读可能会令人困惑。 三,它迫使你明确地做我想要封装的东西。 四,它留下格式不好的空间。 五,涉及到string文字的一次性parsing,比较好的编程风格更像是一个实际的玩笑。
以下也需要浪费实例:
var fracArray = Array.ConvertAll(numDenArray, item => (Fraction)item);
除非使用那些可怕的参差不齐的数组,否则以下使用属性会有相同的问题:
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]; }
这种变化不会强制配对:
foreach (int[,] fracString in fracStrings) { fracArray[index].pair = fracStrings[index]; }
再一次,这个方法是很大的。
这些都是我知道如何派生的想法。 有没有一个好的解决scheme?
我想不出一个优雅,同时内存高效的arrays解决scheme。
但是对于使用C#6 集合初始值设定项function的列表(以及类似),有一个优雅的解决scheme:
public static class Extensions { public static void Add(this ICollection<Fraction> target, int numerator, int denominator) { target.Add(new Fraction(numerator, denominator)); } }
使用该扩展方法,您可以轻松地初始化一个Fraction
列表,例如:
var list = new List<Fraction> { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } };
当然,虽然内存效率不高,但您可以使用它来初始化Fraction
数组:
var array = new List<Fraction> { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } }.ToArray();
或者甚至通过使用隐式数组转换运算符声明列表派生类来使其更简洁:
public class FractionList : List<Fraction> { public static implicit operator Fraction[](FractionList x) => x?.ToArray(); }
然后使用
Fraction[] array = new FractionList { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } };
您可以使用stream畅的界面创build一个分数数组生成器。 这将导致类似的东西
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(); } }
这可以被称为使用
var fractionArray = new FractionArrayBuilder() .Add(1,2) .Add(3,4) .Add(3,1) .Build();
这是一个容易理解的声明。
我已经做了一个小提琴演示。
我可以为您的特定示例考虑的最简洁的方法是为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]); } }
那么你可以像这样初始化它:
var fractions = new Fraction[] { new [] {1, 2}, new [] {3, 4}, new [] {5, 6} };
不幸的是,你仍然需要在每一行上使用new []
,所以我不认为这比正常的数组初始化语法获得了很多。
var fractions = new [] { new Fraction(1, 2), new Fraction(3, 4), new Fraction(5, 6) };
我想你可以用一个短名称来编写一个“local” Func<>
来简化初始化:
Func<int, int, Fraction> f = (x, y) => new Fraction(x, y); var fractions = new [] { f(1, 2), f(3, 4), f(5, 6) };
缺点是你需要添加额外的行(初始化一个Func<>
),无论你想要初始化数组 – 或者在类中有一个私有的静态方法 – 但那么该方法将在整个类的范围内如果它有一个单一的字母名称,这是不理想的。
但是,这种方法的优点是非常灵活。
我玩弄了调用内联函数的想法,但是我真的不确定…
Func<int, int, Fraction> _ = (x, y) => new Fraction(x, y); var fractions = new [] { _(1, 2), _(3, 4), _(5, 6) };