Question

J'expérimente avec des génériques et j'essaie de créer une structure similaire à celle de la classe de jeux de données.
J'ai le code suivant

public struct Column<T>
{
    T value;
    T originalValue;

    public bool HasChanges
    {
        get { return !value.Equals(originalValue); }
    }

    public void AcceptChanges()
    {
        originalValue = value;
    }
}

public class Record
{
    Column<int> id;
    Column<string> name;
    Column<DateTime?> someDate;
    Column<int?> someInt;

    public bool HasChanges
    {
        get
        {
            return id.HasChanges | name.HasChanges | someDate.HasChanges | someInt.HasChanges;
        }
    }

    public void AcceptChanges()
    {
        id.AcceptChanges();
        name.AcceptChanges();
        someDate.AcceptChanges();
        someInt.AcceptChanges();
    }
}

Le problème que j'ai, c'est que lorsque j'ajoute une nouvelle colonne, je dois également l'ajouter à la propriété HasChanges et à la méthode AcceptChanges (). Cela demande juste un peu de refactoring.
La première solution qui me vienne à l’esprit était donc la suivante:

public interface IColumn
{
    bool HasChanges { get; }
    void AcceptChanges();
}

public struct Column<T> : IColumn {...}
public class Record
{
    Column<int> id;
    Column<string> name;
    Column<DateTime?> someDate;
    Column<int?> someInt;

    IColumn[] Columns { get { return new IColumn[] {id, name, someDate, someInt}; }}

    public bool HasChanges
    {
        get
        {
            bool has = false;
            IColumn[] columns = Columns;            //clone and boxing
            for (int i = 0; i < columns.Length; i++)
                has |= columns[i].HasChanges;
            return has;
        }
    }

    public void AcceptChanges()
    {
        IColumn[] columns = Columns;            //clone and boxing
        for (int i = 0; i < columns.Length; i++)
            columns[i].AcceptChanges();         //Here we are changing clone
    }
}

Comme vous pouvez le constater à partir des commentaires, le clonage de struct pose peu de problèmes ici. La solution simple à cela est de changer Column en classe, mais d'après mes tests, il semble que cela augmente l'utilisation de la mémoire d'environ 40% (à cause de chaque métadonnée d'objet), ce qui n'est pas acceptable pour moi.

Ma question est donc la suivante: quelqu'un a-t-il d'autres idées pour créer des méthodes pouvant fonctionner sur différents objets / enregistrements structurés? Une personne de la communauté F # peut peut-être suggérer comment de tels problèmes sont résolus dans des langages fonctionnels et comment cela influe sur les performances et l'utilisation de la mémoire.

Modifier:
sfg merci pour vos suggestions concernant les macros.
Dans Visual Studio 2008, il existe un moteur de modèle intégré (mais pas encore connu) appelé T4. Le but est d’ajouter un fichier '.tt' à mon projet et de créer un modèle qui recherchera toutes mes classes, reconnaîtra ceux qui sont des enregistrements (par exemple, par une interface qu’ils implémentent) et produira des classes partielles avec HasChanges et AcceptChanges ( ) qui appellera uniquement les colonnes que contient la classe.

Quelques liens utiles:
Editeur T4 pour VS
Blog avec des liens et tutoriels sur le T4
Entrée de blog avec un exemple qui utilise EnvDTE pour lire les fichiers de projet

Était-ce utile?

La solution

Comme vous avez demandé des exemples à partir de langages fonctionnels; En un rien de temps, vous pourriez empêcher l'écriture de tout ce code à chaque addition d'une colonne en utilisant une macro pour créer le code pour vous. Malheureusement, je ne pense pas que ce soit possible en C #.

En termes de performances: la macro serait évaluée au moment de la compilation (ce qui ralentirait considérablement la compilation), mais ne causerait aucun ralentissement au moment de l'exécution car le code d'exécution serait le même que celui que vous utiliseriez. écrire manuellement.

Je pense que vous devrez peut-être accepter votre AcceptChanges () d'origine car vous devez accéder aux structures directement par leurs identificateurs si vous souhaitez éviter d'écrire dans des versions clonées.

En d'autres termes: vous avez besoin d'un programme pour l'écrire pour vous, et je ne sais pas comment le faire en C # sans aller trop loin ou perdre plus en performance que vous n'auriez jamais eu en faisant passer les structures à des classes ( exemple: réflexion).

Autres conseils

Vous pouvez utiliser la réflexion pour parcourir les membres et invoquer HasChanges et AcceptChanges. La classe Record peut stocker les métadonnées de réflexion sous forme statique afin d'éviter toute surcharge de mémoire par instance. Cependant, les coûts de performance au moment de l’exécution vont être énormes - vous pourriez également finir par boxer et déballer la colonne, ce qui augmenterait encore le coût.

Honnêtement, il semblerait que vous souhaitiez réellement que ces Column soient des classes, mais que vous ne souhaitiez pas payer le coût d'exécution associé aux classes. Vous essayez donc de les transformer en structures. Je ne pense pas que vous allez trouver un moyen élégant de faire ce que vous voulez. Les structures sont supposées être des types valeur et vous voulez les faire se comporter comme des types référence.

Vous ne pouvez pas stocker efficacement vos colonnes dans un tableau de IColumn . Par conséquent, aucune approche de type tableau ne fonctionnera correctement. Le compilateur n'a aucun moyen de savoir que le tableau IColumn ne contiendra que des structures. En fait, cela ne vous aiderait pas, car il y a encore différents types de structures que vous essayez de brouiller. . Chaque fois que quelqu'un appelle AcceptChanges () ou HasChanges () , vous allez quand même finir par boxer et cloner vos structures, de sorte que je doute sérieusement que rendre votre Column une structure au lieu d'une classe va vous faire économiser beaucoup de mémoire.

Cependant, vous pourriez probablement stocker vos Column directement dans un tableau et les indexer avec un enum. E.g:

public class Record
{
    public enum ColumnNames { ID = 0, Name, Date, Int, NumCols };

    private IColumn [] columns;

    public Record()
    {
        columns = new IColumn[ColumnNames.NumCols];
        columns[ID] = ...
    }

    public bool HasChanges
    {
        get
        {
            bool has = false;
            for (int i = 0; i < columns.Length; i++)
                has |= columns[i].HasChanges;
            return has;
        }
    }

    public void AcceptChanges()
    {
        for (int i = 0; i < columns.Length; i++)
            columns[i].AcceptChanges();
    }
}

Je n'ai pas de compilateur C # à la portée de la main, je ne peux donc pas vérifier si cela fonctionnera ou non, mais l'idée de base devrait fonctionner, même si je n'ai pas obtenu tous les détails. Cependant, je voudrais juste aller de l'avant et leur faire des cours. Vous payez quand même.

La seule façon dont je peux penser à faire ce que vous voulez vraiment faire est d’utiliser Reflection. Cela resterait quand même box / unbox, mais cela vous permettrait de stocker le clone dans le champ, ce qui en ferait la valeur réelle.

public void AcceptChanges()
{
    foreach (FieldInfo field in GetType().GetFields()) {
        if (!typeof(IColumn).IsAssignableFrom(field.FieldType))
            continue; // ignore all non-IColumn fields
        IColumn col = (IColumn)field.GetValue(this); // Boxes storage -> clone
        col.AcceptChanges(); // Works on clone
        field.SetValue(this, col); // Unboxes clone -> storage
    }
}

Que diriez-vous de cela:

public interface IColumn<T>
{
    T Value { get; set; }
    T OriginalValue { get; set; }
}

public struct Column<T> : IColumn<T>
{
    public T Value { get; set; }
    public T OriginalValue { get; set; }
}

public static class ColumnService
{
    public static bool HasChanges<T, S>(T column) where T : IColumn<S>
    {
        return !(column.Value.Equals(column.OriginalValue));
    }

    public static void AcceptChanges<T, S>(T column) where T : IColumn<S>
    {
        column.Value = column.OriginalValue;
    }
}

Le code client est alors:

Column<int> age = new Column<int>();
age.Value = 35;
age.OriginalValue = 34;

if (ColumnService.HasChanges<Column<int>, int>(age))
{
    ColumnService.AcceptChanges<Column<int>, int>(age);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top