Вопрос

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

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

Проблема, с которой я сталкиваюсь, заключается в том, что когда я добавляю новый столбец, мне нужно добавить его также в свойство hashchanges и метод 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
    }
}

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

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

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

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

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

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

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

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 #, поэтому я не могу проверить, сработает это или нет, но основная идея должна сработать, даже если я не разобрался во всех деталях правильно.Тем не менее, я бы просто пошел дальше и провел для них занятия.Ты все равно за это платишь.

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

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

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

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);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top