35 votes

Initialisation élégante d'un tableau d'instances de classe en C #

Disons que j'ai une classe comme ceci:

public class Fraction
{
   int numerator;
   int denominator;

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

   // And then a bunch of other methods
}

Je veux initialiser un tableau d'une belle manière, et ce post est une grande liste de démarches qui sont sujettes à erreur ou syntaxiquement lourd.

Bien sûr, un constructeur tableau serait bien, mais il n'y a pas une telle chose:

public Fraction[](params int[] numbers)

Donc, je suis obligé d'utiliser une méthode comme

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

ce qui est relativement maladroit, mais je ne vois pas un moyen de contourner cela.

Les deux formes sont sujettes à erreur, parce qu'un utilisateur pouvait à tort de passer un nombre impair d'éléments, peut-être parce qu'il a ignoré une valeur, ce qui risquerait de laisser la fonction de gratter sa tête en se demandant ce que l'utilisateur en fait voulu. Il peut lancer une exception, mais ensuite, l'utilisateur nécessité de try/catch. Je préfère ne pas l'imposer à l'utilisateur, si possible. Donc, nous allons appliquer les paires.

public static Fraction[] CreateArray(params int[2][] pairs)

Mais vous ne pouvez pas appeler cela CreateArray de bien belle façon, comme

Fraction.CreateArray({0,1}, {1,2}, {1,3}, {1,7}, {1,42});

Vous ne pouvez même pas faire

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

Notez que ce serait très bien fonctionner en C++ (je suis sûr).

Vous êtes forcé de faire l'une des opérations suivantes au lieu de cela, ce qui est abominable. La syntaxe est terrible et il semble vraiment bizarre d'utiliser un tableau en escalier lorsque tous les éléments ont la même longueur.

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

De même, Python-style tuples sont illégales et la version C# est dégueulasse:

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

L'utilisation d'un pur tableau 2D pourrait prendre la forme suivante, mais il est illégal, et je suis sûr qu'il n'y a aucun moyen légal pour l'exprimer:

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

Ce n'est pas le respect des paires:

public static Fraction[] CreateArray(int[,] twoByXArray)

OK, comment sur

public static Fraction[] CreateArray(int[] numerators, int[] denominators)

Mais alors les deux tableaux peuvent avoir des longueurs différentes. C++ permet

public static Fraction[] CreateArray<int N>(int[N] numerators, int[N] denominators)

mais bon, ce n'est pas le C++, est-il?

Ce genre de chose est illégal:

public static implicit operator Fraction[](params int[2][] pairs)

et impraticable de toute façon, à nouveau en raison de l'odieux de la syntaxe:

Fraction[] fracArray = new Fraction[](new int[2]{0,1}, /*etc*/ );

Ce pourrait être sympa:

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

Ensuite, vous pouvez faire

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

Je n'aime pas cette approche pour cinq raisons. L'un, de l'implicite cast inévitablement instancie un nouvel objet, mais nous avons déjà une très bonne, à savoir celui que nous essayons de les initialiser. De deux, il peut être difficile à lire. De trois, il vous oblige à faire explicitement ce que je voulais pour encapsuler dans la première place. Quatre, il laisse place à une mauvaise mise en forme. Cinq, il implique un temps d'analyse de littéraux de chaîne, qui ressemble plus à une blague qu'un bon style de programmation.

La suite nécessite également un gaspillage d'instanciation:

var fracArray = Array.ConvertAll(numDenArray, item => (Fraction)item);

La suite de l'utilisation d'un bien a le même problème, sauf si vous utilisez ces terribles les tableaux irréguliers:

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

Cette variation n'est pas le respect des paires:

foreach (int[,] fracString in fracStrings) {
    fracArray[index].pair = fracStrings[index];
}

Encore une fois, cette approche est grand de toute façon.

Ce sont toutes les idées, je sais comment m'en tirer. Est-il une bonne solution?

39voto

Ivan Stoev Points 1156

Je ne peux pas penser à une élégante, et en même temps la mémoire de solution efficace pour le tableau.

Mais il existe une solution élégante pour la liste (et autres) en utilisant le C# 6 initialiseur de collection fonctionnalité:

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

Avec cette extension de la méthode en place, vous pouvez facilement initialiser un Fraction liste par exemple:

var list = new List<Fraction> { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } };

Et, bien sûr, mais pas efficace en terme de mémoire, vous pouvez l'utiliser pour initialiser Fraction tableau:

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

ou même de les rendre plus concise par la déclaration d'une liste de dérivés de la classe avec le tableau implicite opérateur de conversion:

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

et ensuite utiliser

Fraction[] array = new FractionList { { 0, 1 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 42 } };

8voto

Andy Nichols Points 2373

Vous pouvez créer un générateur de tableaux de fractions avec une interface fluide. Cela mènerait à quelque chose comme

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

qui peut être appelé en utilisant

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

ce qui est une déclaration facile à comprendre.

J'ai fait un violon pour démontrer.

7voto

Matthew Watson Points 30804

La façon la plus succincte que je pense de votre exemple est celui de l'écriture d'un opérateur implicite pour la Fraction de la classe:

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

Ensuite, vous pouvez l'initialiser comme ceci:

var fractions = new Fraction[]
{
    new [] {1, 2},
    new [] {3, 4},
    new [] {5, 6}
};

Malheureusement, vous avez encore besoin de l' new [] sur chaque ligne, donc je ne pense pas que ce gains très bien sur le tableau normal initialisation de la syntaxe:

var fractions = new []
{
    new Fraction(1, 2),
    new Fraction(3, 4),
    new Fraction(5, 6)
};

Je suppose que vous pourriez écrire un "local" Func<> avec un nom court pour simplifier l'initialisation quelque peu:

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

var fractions = new []
{
    f(1, 2),
    f(3, 4),
    f(5, 6)
};

L'inconvénient est que vous devez ajouter cette ligne supplémentaire (initialiser une Func<>) chaque fois que vous vouliez, pour initialiser le tableau - ou avez une méthode statique de la classe au lieu - mais alors que la méthode pourrait être portée tout au long de la classe, ce qui n'est pas l'idéal si on a un seul nom de la lettre.

Cependant, l'avantage de cette approche est qu'il est très flexible.

J'ai joué avec l'idée d'appeler la fonction inline _, mais je ne suis vraiment pas sûr...

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

var fractions = new []
{
    _(1, 2),
    _(3, 4),
    _(5, 6)
};

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X