класс с полями valueTypes и боксом - PullRequest
1 голос
/ 27 сентября 2008

Я экспериментирую с дженериками и пытаюсь создать структуру, аналогичную классу набора данных.
У меня есть следующий код

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

Проблема у меня заключается в том, что когда я добавляю новый столбец, мне нужно добавить его также в свойстве HasChanges и методе AcceptChanges (). Это просто требует некоторого рефакторинга.
Итак, первое решение, которое пришло мне в голову, было примерно таким:

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

Как видно из комментариев, у нас мало проблем со структурным клонированием. Простым решением этого является изменение столбца на класс, но из моих тестов кажется, что он увеличивает использование памяти на ~ 40% (из-за метаданных каждого объекта), что для меня неприемлемо.

Поэтому у меня вопрос: есть ли у кого-нибудь еще идеи, как создавать методы, которые могут работать с различными структурированными объектами / записями? Может быть, кто-то из сообщества F # может подсказать, как такие проблемы решаются на функциональных языках и как это влияет на производительность и использование памяти.

Edit:
sfg спасибо за предложение о макросах.
В Visual Studio 2008 есть встроенный (но не очень известный) шаблонизатор под названием T4. Весь смысл в том, чтобы добавить файл «.tt» в мой проект и создать шаблон, который будет искать все мои классы, каким-то образом распознавать те, которые являются записями (например, по некоторому интерфейсу, который они реализуют) и создавать частичные классы с HasChanges и AcceptChanges ( ) который будет вызывать только столбцы, содержащиеся в классе.

Некоторые полезные ссылки:
T4 Editor для VS
Блог со ссылками и учебными пособиями о T4
Запись в блоге с примером, использующим EnvDTE для чтения файлов проекта

Ответы [ 5 ]

1 голос
/ 27 сентября 2008

Как вы просили примеры из функциональных языков; в lisp вы можете предотвратить написание всего этого кода при каждом добавлении столбца, используя макрос, чтобы провернуть код для вас. К сожалению, я не думаю, что это возможно в C #.

С точки зрения производительности: макрос будет оцениваться во время компиляции (таким образом, небольшая задержка компиляции), но не вызовет замедления во время выполнения, так как код времени выполнения будет таким же, как и вы пишите вручную.

Я думаю, что вам, возможно, придется принять ваш оригинальный AcceptChanges (), так как вам нужен прямой доступ к структурам по их идентификаторам, если вы хотите избежать записи в клонированные версии.

Другими словами: вам нужна программа, чтобы написать программу для вас, и я не знаю, как сделать это в C #, не вдаваясь в невероятные длины или теряя в производительности больше, чем когда-либо, переключая структуры на классы ( например, отражение).

0 голосов
/ 27 сентября 2008

Как насчет этого:

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

Тогда код клиента:

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);
}
0 голосов
/ 27 сентября 2008

Единственный способ сделать то, что вы действительно хотите сделать, - это использовать Reflection. Это все равно будет блокировать / распаковывать, но позволит сохранить клон обратно в поле, фактически сделав его реальным значением.

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
    }
}
0 голосов
/ 27 сентября 2008

Честно говоря, звучит так, будто вы действительно хотите, чтобы эти Column были классами, но не хотите оплачивать затраты времени выполнения, связанные с классами, поэтому вы пытаетесь сделать их структурами. Я не думаю, что вы найдете элегантный способ делать то, что вы хотите. Структуры должны быть типами значений, и вы хотите, чтобы они вели себя как ссылочные типы.

Вы не можете эффективно хранить ваши столбцы в массиве IColumn с, поэтому подход с использованием массива не будет работать хорошо. Компилятор не может знать, что массив IColumn будет содержать только структуры, и, действительно, это не поможет, потому что существуют различные типы структур, которые вы пытаетесь вставить в него. Каждый раз, когда кто-то звонит AcceptChanges() или HasChanges(), вы все равно будете заканчивать боксом и клонированием своих структур, поэтому я серьезно сомневаюсь, что превращение вашей Column в структуру вместо класса сэкономит вам много памяти.

Однако вы могли бы , вероятно, хранить ваши Column непосредственно в массиве и индексировать их с помощью перечисления. Например:

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

У меня нет под рукой компилятора C #, поэтому я не могу проверить, сработает ли это, или нет, но основная идея должна работать, даже если я не понял все детали правильно. Тем не менее, я просто пошел бы и сделал их уроки. Вы все равно платите за это.

0 голосов
/ 27 сентября 2008

Вы можете использовать рефлексию для перебора членов и вызова HasChanges и AcceptChanges. Класс Record может хранить метаданные отражения как статические, поэтому нет затрат на память для каждого экземпляра. Тем не менее, затраты на производительность во время выполнения будут огромными - вы также можете положить в коробку и распаковать столбец, что увеличит стоимость.

...