Frage

Ich bin mit Generika zu experimentieren, und ich versuche Struktur zu schaffen, ähnlich wie Dataset-Klasse.
Ich habe folgenden Code

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

Problem ist, dass ich, wenn ich neue Spalte hinzufügen, ich muss es auch hinzufügen, in Haschanges Immobilien und AcceptChanges () -Methode. Dieser fragt nur für einige Refactoring.
Also erste Lösung, die mir in den Sinn Cames war so etwas wie folgt aus:

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

Wie Sie Kommentare sehen wir haben einige Probleme hier mit struct Klonen. Einfache Lösung für dieses Problem ist Spalte Klasse zu ändern, aber aus meinen Tests scheint es, dass es die Speichernutzung von ~ 40% erhöht (weil jedes Objekt-Metadaten), die für mich nicht akzeptabel ist.

Also meine Frage ist: Hat jemand noch andere Ideen, wie Methoden erstellen, die auf verschiedenen strukturierten Objekten / Datensätze arbeiten können? Vielleicht kann jemand von F # Gemeinschaft vorschlagen, wie diese Probleme gelöst sind in funktionalen Sprachen und dessen Einfluss auf Leistung und Speichernutzung.

Bearbeiten
SFG Dank für einen Vorschlag über Makros.
In Visual Studio 2008 wird eingebaut (aber nicht so bekannt) Template-Engine T4 bezeichnet. Tha springende Punkt ist ‚.tt‘ Datei in meinem Projekt hinzuzufügen und eine Vorlage zu erstellen, die alle meine Klassen sucht, erkennen irgendwie diejenigen, die es Aufzeichnungen (zum Beispiel durch eine Schnittstelle sie implementieren) und produzieren partielle Klassen mit Haschanges und AcceptChanges ( ), die nur Spalten der Klasse enthalten rufen.
Einige nützliche Links:
T4 Editor für VS
Blog mit Links und tutorials über T4
Blog-Eintrag mit Beispiel zu lesen, die EnvDTE Projektdateien

War es hilfreich?

Lösung

Wie Sie Beispiele von funktionalen Sprachen gefragt; in Lisp konnte man das Schreiben von all diesen Code auf jeder Zugabe einer Spalte mithilfe eines Makros Kurbel den Code für Sie verhindern. Leider glaube ich nicht, dass in C # möglich ist.

In Bezug auf Leistung: das Makro bei der Kompilierung ausgewertet werden würde (also Kompilation eine winzige Menge verlangsamt), würde aber keine Verlangsamung zur Laufzeit als Laufzeitcode verursacht das gleiche wäre wie das, was würden Sie schreiben manuell.

Ich glaube, Sie könnten Ihre ursprüngliche AcceptChanges akzeptieren müssen (), wie Sie die structs direkt durch ihre Kennungen zugreifen müssen, wenn Sie geklonten Versionen vermeiden, schreiben möchten.

Mit anderen Worten: Sie müssen ein Programm, das Programm für Sie zu schreiben, und ich weiß nicht, wie das, ohne dabei zu außergewöhnlichen Längen in C # zu tun oder mehr Leistung zu verlieren, als Sie jemals durch Umschalten der structs Klassen würde ( zB Reflexion).

Andere Tipps

könnten Sie verwenden Reflektion, um die Mitglieder iterieren und Haschanges und AcceptChanges aufzurufen. Die Record-Klasse konnte die Reflexion Metadaten als statisch speichern, so gibt es keinen pro-Instanz Speicher-Overhead. Allerdings sind die Leistungskosten zur Laufzeit groß sein werden -. Sie könnten auch Boxen und Unboxing die Säule am Ende der noch die Kosten erhöhen würde

Ehrlich gesagt, es klingt wie Sie wirklich diese Columns wollen Klassen sein, aber nicht wollen, die Laufzeitkosten mit Klassen zugeordnet bezahlen, so dass Sie versuchen, sie structs zu machen. Ich glaube nicht, dass Sie einen eleganten Weg finden werden zu tun, was Sie wollen. Structs sollen Werttypen sein, und Sie wollen sie wie Referenztypen machen verhalten.

Sie können nicht effizient speichern Sie Ihre Spalten in einem Array von IColumns, so dass kein Array Ansatz ist gut zur Arbeit zu gehen. Der Compiler hat keine Möglichkeit, zu wissen, dass die IColumn Array nur structs halten werden, und in der Tat, es würde nicht helfen, wenn es so wäre, denn es gibt immer noch verschiedene Arten von Strukturen sind Sie dort zu Marmelade versuchen. Jedes Mal, wenn jemand ruft AcceptChanges() oder HasChanges(), Sie gehen nach oben boxen beenden und Ihre structs Klonen ohnehin, so dass ich bezweifle ernsthaft, dass Ihr Column eine Struktur statt einer Klasse machen wird Sie viel Speicher speichern.

Allerdings Sie könnte wahrscheinlich speichern Sie Ihre Columns direkt in einem Array und indizieren sie mit einer Enum. Z. B:

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

Ich habe nicht einen C # Compiler praktisch, so kann ich, wenn nicht überprüfen, um zu sehen, dass funktioniert oder nicht, aber die Grundidee sollte funktionieren, auch wenn ich nicht alles in Ordnung und die Details bekommen. Allerdings würde ich einfach weitermachen und ihnen Unterricht machen. Sie sind dafür zu bezahlen sowieso.

Die einzige Möglichkeit, die ich denken kann zu tun, was Sie wirklich wollen, ist Reflexion zu verwenden. Dies würde immer noch box / unbox, aber es würde können Sie den Klon zurück in das Feld speichern, effektiv es den realen Wert zu machen.

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

Wie wäre es damit:

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

Der Client-Code ist dann:

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);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top