Pergunta

Eu estou experimentando com genéricos e eu estou tentando criar estrutura semelhante a classe DataSet.
Eu tenho seguinte 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();
    }
}

O problema que tenho é que quando eu adicionar nova coluna eu preciso adicioná-lo também na HasChanges propriedade e AcceptChanges (método). Isso só solicita algumas refatoração.
Então, a primeira solução que cames à cabeça foi algo como isto:

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 você pode ver a partir dos comentários temos alguns problemas aqui com a clonagem struct. solução simples para isso é mudar Coluna para a aula, mas de meus testes parece que aumenta o uso de memória por ~ 40% (porque de cada metadados objeto) que não é aceitável para mim.

Então, minha pergunta é: será que alguém tem alguma outra idéia como criar métodos que podem funcionar em diferentes estruturados objetos / registros? Talvez alguém da F # comunidade pode sugerir como tais problemas são resolvidos em linguagens funcionais e como o uso de TI afeta o desempenho e memória.

Editar:
SFG obrigado pela sugestão sobre macros.
Em Visual Studio 2008 não é built-in (mas não tão conhecido) modelo de motor chamado T4. Tha questão toda é para adicionar arquivo '.tt' para meu projeto e criar um modelo que irá procurar todas as minhas aulas, reconhecer de alguma forma os que são registros (por exemplo, por alguma interface que implementar) e produzem classes parciais com HasChanges e AcceptChanges ( ) que vai chamar apenas colunas da classe conter.
Alguns links úteis:
T4 Editor for VS
Blog com links e tutoriais sobre T4
entrada de blog com exemplo que usa EnvDTE para ler arquivos de projeto

Foi útil?

Solução

Como você pediu exemplos de linguagens funcionais; em lisp você poderia evitar a escrita de todo esse código em cima de cada adição de uma coluna usando uma macro para acionar o código para você. Infelizmente, eu não acho que é possível em C #.

Em termos de desempenho: o macro seria avaliada em tempo de compilação (retardando assim a compilação uma pequena quantidade), mas não causaria slow-down em tempo de execução como o código de tempo de execução seria a mesma coisa que você faria escrever manualmente.

Eu acho que você pode ter que aceitar suas AcceptChanges originais () que você precisa para acessar as estruturas diretamente por seus identificadores se você quiser evitar escrever a versões clonadas.

Em outras palavras: você precisa de um programa para escrever o programa para você, e eu não sei como fazer isso em C # sem ir a extremos ou perder mais no desempenho do que você jamais iria mudando as estruturas às classes ( por exemplo reflexão).

Outras dicas

Você pode usar a reflexão para iterar sobre os membros e invocar HasChanges e AcceptChanges. A classe de registro pode armazenar os metadados reflexão como estático para que não haja sobrecarga de memória por instância. No entanto, o custo de desempenho em tempo de execução vai ser enorme -. Você também pode acabar boxing e unboxing a coluna que gostaria de acrescentar ainda mais ao custo

Honestamente, parece que você realmente quer estes Columns ser classes, mas não querem pagar o custo de tempo de execução associado com aulas, então você está tentando fazê-los ser estruturas. Eu não acho que você vai encontrar uma maneira elegante de fazer o que quiser. Estruturas são supostamente tipos de valor, e você quer fazê-los comportar-se como tipos de referência.

Você não pode de forma eficiente armazenar suas colunas em uma matriz de IColumns, então nenhuma abordagem variedade vai funcionar bem. O compilador não tem como saber que a matriz IColumn só vai manter estruturas e, de fato, não ajudaria se ele fez, porque ainda há diferentes tipos de estruturas que você está tentando jam lá. Toda vez que alguém chama AcceptChanges() ou HasChanges(), você vai acabar boxe e clonagem de suas estruturas de qualquer maneira, então eu duvido seriamente que fazer a sua Column um struct em vez de uma classe vai poupar muita memória.

No entanto, você poderia , provavelmente armazenar seus Columns diretamente em um array e indexá-los com um enum. Por exemplo:

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

Eu não tenho um compilador C # calhar, por isso não posso verificar para ver se isso vai funcionar ou não, mas a idéia básica deve trabalhar, mesmo se eu não obter todos os detalhes direito. No entanto, eu tinha acabado de ir em frente e torná-los classes. Você está pagando por isso de qualquer maneira.

A única maneira que eu posso pensar em fazer o que você realmente quer fazer é usar a reflexão. Isso ainda seria BOX / unbox, mas faria permitem que você armazene a parte de trás clone no campo, efetivamente tornando-o o 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
    }
}

Como sobre isto:

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

O código do cliente é então:

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top