2016-04-20 7 views
35

Скажем, у меня есть класс, как это:Элегантная инициализация массива экземпляров класса в C#

public class Fraction 
{ 
    int numerator; 
    int denominator; 

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

    // And then a bunch of other methods 
} 

Я хочу, чтобы инициализировать массив из них в хорошем пути, и этот пост является большой список подходы, подверженные ошибкам или синтаксически громоздкие.

Конечно Конструктор массива было бы хорошо, но нет такой вещи:

public Fraction[](params int[] numbers) 

Так что я вынужден использовать метод как

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

, которая является относительно неуклюжими, но я не видишь пути вокруг него.

Обе формы подвержены ошибкам, потому что пользователь может ошибочно передать нечетное количество параметров, возможно, потому что он/она пропустил значение, которое оставило бы функцию царапающей голову, задаваясь вопросом, чего действительно хочет пользователь. Это может вызвать исключение, но тогда пользователю нужно будет попробовать/поймать. Я бы предпочел не налагать это на пользователя, если это возможно. Итак, давайте навяжем пары.

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# версия неприглядное:

Fraction.CreateArray(new Tuple<int,int>(0,1), /*etc*/); 

Использование чистого 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}}); 

Это не навязывает пары:

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]; 
} 

Мне не нравится этот подход по пяти причинам. Во-первых, неявный бросок неизбежно создает новый объект, но у нас уже есть отлично, а именно тот, который мы пытаемся инициализировать. Во-вторых, это может ввести в заблуждение. В-третьих, это заставляет вас явно делать то, что я хотел инкапсулировать в первую очередь. Четыре, это оставляет место для плохого форматирования. Пять, это включает в себя разовое разбор струнных литералов, что больше похоже на практическую шутку, чем на хороший стиль программирования.

Следующая также требует расточительного экземпляра:

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]; 
} 

Опять же, этот подход в любом случае большой.

Это все идеи, которые я знаю, как получить. Есть ли хорошее решение?

+1

Существует [связанный пост] (http://stackoverflow.com/questions/23534114/initialization-of-const-array-of-struct), который предлагает трюк, используя список объектов. –

+0

@Axel - Бинго. – MackTuesday

ответ

38

Я не могу придумать элегантное и в то же время эффективное решение памяти для массива.

Но есть элегантное решение для списка (и аналогичного) с использованием C# 6 collection initializer особенности:

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 } }; 
+1

Мне это нравится для 'List <>'! –

+5

Отличное решение! И эффективность памяти не должна беспокоить, если мы говорим о написанных вручную кортежах в коде! Если их больше 1000, они должны быть в файле ресурсов! – Falco

+0

Для этого требуется определенная версия C#? Я попробовал 'новый список {{0, 1}, {1, 2}, [...]' пример, но он говорит, что добавление не принимает 2 аргумента, хотя расширение находится в области видимости. (Примечание: я не спрашиваю) –

7

Самым лаконичным способом я могу думать для вашего конкретного примера включает в себя написание неявного оператора для класса Фракция:

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) 
}; 

Я полагаю, вы могли бы написать «местный» 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) 
}; 
+0

Да, синтаксис лучше, но все еще неуклюжий. Кроме того, я бы сказал, что это ошибка, потому что параметр данных может не содержать двух элементов. В худшем случае он имеет только 0 или 1, что вызовет исключение, а потенциальные исключения затрудняют реализацию техники. – MackTuesday

+2

Вместо локального 'Func', я бы подумал сделать его' public static' методом для некоторого класса, к которому вы можете получить доступ с помощью 'using static'. – svick

+0

Великие наблюдения, я ценю все ваши прозрения и объяснения, я нашел «подчеркивание» смешным. –

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(); 
    } 
} 

, который можно назвать, используя

var fractionArray = new FractionArrayBuilder() 
    .Add(1,2) 
    .Add(3,4) 
    .Add(3,1) 
    .Build(); 

, который легко понять заявление.

Я продемонстрировал fiddle.

+1

вы можете перенести первый аргумент в метод 'Add' и просто вернуть' this' –

+1

Это не скомпилируется: если 'Add()' должен быть методом расширения, он должен быть 'static' , Но я не вижу причин, почему это не просто метод экземпляра. – svick

+0

Вы оба правы. Я отредактировал. –

Смежные вопросы