Pregunta

Estoy experimentando con genéricos y estoy tratando de crear una estructura similar a la clase Dataset.
Tengo el siguiente código

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

El problema que tengo es que cuando agrego una nueva columna, necesito agregarla también en la propiedad HasChanges y en el método AcceptChanges (). Esto solo pide un poco de refactorización.
Así que la primera solución que me vino a la mente fue algo como esto:

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

Como se puede ver en los comentarios, aquí tenemos algunos problemas con la clonación de estructuras. Una solución simple para esto es cambiar la columna a la clase, pero según mis pruebas, parece que aumenta el uso de la memoria en un 40% (debido a cada metadata del objeto), lo cual no es aceptable para mí.

Entonces, mi pregunta es: ¿Alguien tiene alguna otra idea sobre cómo crear métodos que puedan funcionar en diferentes objetos / registros estructurados? Tal vez alguien de la comunidad F # pueda sugerir cómo se resuelven estos problemas en lenguajes funcionales y cómo afecta el rendimiento y el uso de la memoria.

Editar:
sfg gracias por sugerencia sobre macros.
En Visual Studio 2008 hay un motor de plantillas integrado (pero no tan conocido) llamado T4. Todo el punto es agregar el archivo '.tt' a mi proyecto y crear una plantilla que busque en todas mis clases, reconozca de alguna manera los que son registros (por ejemplo, por alguna interfaz que implementan) y produzca clases parciales con HasChanges y AcceptChanges ( ) que llamará solo a las columnas que contiene la clase.

Algunos enlaces útiles:
Editor de T4 para VS
Blog con enlaces y tutoriales sobre T4
entrada de blog con ejemplo que usa EnvDTE para leer archivos de proyecto

¿Fue útil?

Solución

Como pediste ejemplos de lenguajes funcionales; En lisp podría prevenir la escritura de todo ese código en cada adición de una columna usando una macro para arrancar el código por usted. Lamentablemente, no creo que sea posible en C #.

En términos de rendimiento: la macro se evaluaría en tiempo de compilación (por lo tanto, ralentizando la compilación en una cantidad muy pequeña), pero no causaría una ralentización en el tiempo de ejecución, ya que el código de tiempo de ejecución sería el mismo que lo que haría escribir manualmente.

Creo que es posible que tenga que aceptar sus AcceptChanges () originales, ya que necesita acceder a las estructuras directamente por sus identificadores si desea evitar escribir en versiones clonadas.

En otras palabras: necesita un programa para escribir el programa por usted, y no sé cómo hacerlo en C # sin tener que llegar a longitudes extraordinarias o perder más en el rendimiento de lo que lo haría al cambiar las estructuras a clases ( por ejemplo, la reflexión).

Otros consejos

Puede utilizar la reflexión para iterar sobre los miembros e invocar HasChanges y AcceptChanges. La clase de grabación podría almacenar los metadatos de reflexión como estáticos, por lo que no hay sobrecarga de memoria por instancia. Sin embargo, el costo de rendimiento en tiempo de ejecución será enorme, también podría terminar encajonando y desempaquetando la columna, lo que aumentaría aún más el costo.

Honestamente, parece que realmente quieres que estas Column sean clases, pero no quieres pagar el costo de tiempo de ejecución asociado con las clases, por lo que estás tratando de que sean estructuras. No creo que encuentres una forma elegante de hacer lo que quieres. Se supone que las estructuras son tipos de valor y desea que se comporten como tipos de referencia.

No puede almacenar sus columnas de manera eficiente en una matriz de IColumn , por lo que ningún enfoque de matriz funcionará bien. El compilador no tiene forma de saber que la matriz IColumn solo tendrá estructuras, y de hecho, no ayudaría si lo hiciera, porque todavía hay diferentes tipos de estructuras que intentas bloquear allí. . Cada vez que alguien llame a AcceptChanges () o HasChanges () , de todos modos acabarás encajonando y clonando tus estructuras, así que dudo seriamente que al hacer tu La columna una estructura en lugar de una clase le ahorrará mucha memoria.

Sin embargo, podría almacenar sus Column directamente en una matriz e indexarlos con una enumeración. 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();
    }
}

No tengo un compilador de C # a la mano, así que no puedo verificar si eso funcionará o no, pero la idea básica debería funcionar, incluso si no obtuve todos los detalles correctamente. Sin embargo, me gustaría seguir adelante y hacerles clases. De todos modos, lo estás pagando.

La única forma en que puedo pensar para hacer lo que realmente quieres hacer es usar Reflexión. Esto todavía encajonaría / unbox, pero le permitiría almacenar el clon nuevamente en el campo, convirtiéndolo efectivamente en el valor real.

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

¿Qué tal esto?

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

El código del cliente es entonces:

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);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top