Domanda

Sto sperimentando generici e sto cercando di creare una struttura simile alla classe Dataset.
Ho il seguente codice

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

Il problema che ho è che quando aggiungo una nuova colonna devo aggiungerla anche nella proprietà HasChanges e nel metodo AcceptChanges (). Questo richiede solo qualche refactoring.
Quindi la prima soluzione che mi è venuta in mente è stata una cosa del genere:

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

Come puoi vedere dai commenti, qui abbiamo pochi problemi con la clonazione di strutture. La soluzione semplice a questo è cambiare la colonna in classe, ma dai miei test sembra che aumenti l'utilizzo della memoria del ~ 40% (a causa di ogni metadata di oggetto) che non è accettabile per me.

Quindi la mia domanda è: qualcuno ha altre idee su come creare metodi in grado di lavorare su diversi oggetti / record strutturati? Forse qualcuno della comunità F # può suggerire come risolvere tali problemi nei linguaggi funzionali e in che modo influisce sulle prestazioni e sull'utilizzo della memoria.

Modifica
sfg grazie per il suggerimento sulle macro.
In Visual Studio 2008 è disponibile un motore modello incorporato (ma non così noto) chiamato T4. L'intero punto è aggiungere il file '.tt' al mio progetto e creare un modello che cercherà tutte le mie classi, riconoscerà in qualche modo quelli che sono record (ad esempio da qualche interfaccia che implementano) e producendo classi parziali con HasChanges e AcceptChanges ( ) che chiamerà solo le colonne contenute nella classe.

Alcuni link utili:
Editor T4 per VS
Blog con link e tutorial su T4
Voce di blog con esempio che utilizza EnvDTE per leggere i file di progetto

È stato utile?

Soluzione

Come hai chiesto esempi dai linguaggi funzionali; in lisp potresti impedire la scrittura di tutto quel codice su ogni aggiunta di una colonna usando una macro per far girare il codice per te. Purtroppo, non penso che sia possibile in C #.

In termini di prestazioni: la macro verrebbe valutata in fase di compilazione (rallentando così una piccola quantità), ma non causerebbe alcun rallentamento in fase di esecuzione poiché il codice di runtime sarebbe uguale a quello che si farebbe scrivere manualmente.

Penso che potresti dover accettare il AcceptChanges originale () in quanto devi accedere direttamente alle strutture tramite i loro identificatori se vuoi evitare di scrivere su versioni clonate.

In altre parole: hai bisogno di un programma per scrivere il programma per te, e non so come farlo in C # senza andare a lunghezze straordinarie o perdere più prestazioni di quanto potresti mai passando le strutture in classi ( es. riflessione).

Altri suggerimenti

È possibile utilizzare la riflessione per scorrere i membri e invocare HasChanges e AcceptChanges. La classe Record potrebbe archiviare i metadati di riflessione come statici, quindi non esiste un sovraccarico di memoria per istanza. Tuttavia, il costo delle prestazioni in fase di runtime sarà enorme - potresti anche finire per inscatolare e deselezionare la colonna che aggiungerebbe ancora di più al costo.

Onestamente, sembra che tu voglia davvero che questi Column siano classi, ma non vuoi pagare i costi di runtime associati alle classi, quindi stai cercando di farli diventare strutture. Non credo che troverai un modo elegante per fare quello che vuoi. Le strutture dovrebbero essere tipi di valore e si desidera che si comportino come tipi di riferimento.

Non puoi archiviare in modo efficiente le tue colonne in una matrice di IColumn , quindi nessun approccio di matrice funzionerà bene. Il compilatore non ha modo di sapere che l'array IColumn conterrà solo le strutture, e in effetti non sarebbe utile se lo facesse, perché ci sono ancora diversi tipi di strutture che stai cercando di bloccare . Ogni volta che qualcuno chiama AcceptChanges () o HasChanges () , finirai comunque per inscatolare e clonare le tue strutture, quindi dubito seriamente che creare il tuo La colonna una struttura anziché una classe ti farà risparmiare molta memoria.

Tuttavia, potresti probabilmente memorizzare i tuoi Column direttamente in un array e indicizzarli con un enum. Per esempio:

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

Non ho un compilatore C # a portata di mano, quindi non posso controllare per vedere se funzionerà o meno, ma l'idea di base dovrebbe funzionare, anche se non ho ottenuto tutti i dettagli giusti. Tuttavia, vorrei solo andare avanti e fare loro delle lezioni. Lo stai comunque pagando.

L'unico modo in cui riesco a pensare di fare quello che vuoi veramente è usare Reflection. Questo rimarrebbe comunque box / unbox, ma ti ti permetterebbe di salvare il clone nel campo, rendendolo effettivamente il valore reale.

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

Che ne dici di questo:

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

Il codice client è quindi:

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);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top