Аксессуар для отформатированного подсписка словаря возможен без создания нового объекта каждый раз?

во-первых - да, я рассмотрел этот вопрос: Создание объекта в геттерах плохая практика?. Я также не говорю об инициализации объекта в аксессорах/мутаторах, речь идет о конкретной части объекта, которую я хочу вернуть определенным образом.

Мой вопрос более конкретен; Это не обязательно относится только к С#, однако в настоящее время я ищу решение для реализации в моем проекте С#.

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

class Class
{
    // Some other properties...
    // ....

    private Dictionary<DateTime, decimal> dict;

    public Class(Dictionary<DateTime, decimal> dict)
    {
        this.dict = dict;
    }

    private string FormatTheWayIWant(decimal dt)
    {
        // Format decimal value.
        string s = String.Format("{0:F}", dt);
        return s;
    }

    public ReadOnlyCollection<DateTime> DateTimes
    {
        get { return new ReadOnlyCollection<DateTime>(this.dict.Keys.ToList()); }
    }

    public ReadOnlyCollection<decimal> Values
    {
        get { return new ReadOnlyCollection<decimal>(this.dict.Values.ToList()); }
    }

    public ReadOnlyCollection<string> FormattedStrings
    {
        get
        {
            // Format each decimal value they way I want.
            List<string> list = new List<string>();
            foreach (decimal dt in dict.Keys)
            {
                list.Add(FormatTheWayIWant(dt));
            }
            return new ReadOnlyCollection<string>(list);
        }
    }
}

Таким образом, я могу сделать следующие вызовы (что и является моей целью!):

DateTime dateTime = DateTimes[0];
decimal s = Values[0];
string formattedS = FormattedStrings[0];

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

Альтернативы, о которых я думал, следующие:

  1. Я мог бы расширить класс decimal и реализовать собственный метод ToString().
  2. Или перезапишите класс KeyValuePair<DateTime, decimal> и используйте индексатор в моем классе.
  3. Или я создаю метод с параметром для индекса и возвращаю только одну отформатированную строку.
  4. Или у меня может быть собственный список для доступа, который обновляется в методе set для моего словаря каждый раз, когда я обновляю словарь.

У меня есть вопрос: есть ли способ заставить это работать с аксессором вместо метода, создавая пользовательские классы или создавая странные побочные эффекты для других объектов при присвоении значения?

Заранее спасибо.


person Yeehaw    schedule 04.12.2013    source источник
comment
Я отредактировал вопрос и добавил десятичные знаки вместо строк или значений словаря. Это должно прояснить ситуацию.   -  person Yeehaw    schedule 05.12.2013


Ответы (4)


Конечно, это можно сделать с помощью аксессора. Вам просто нужно создать 3 отдельных класса для каждого желаемого элемента вашей обработанной коллекции. Эти классы должны иметь свои собственные индексаторы, чтобы вы могли получить доступ к элементам в виде списка. Разница будет заключаться в том, что они вычисляют каждый элемент по запросу (что называется ленивой инициализацией). Так что это будет выглядеть так (пример для вашего FormattedStrings):

class Class
{
    // ...

    MyFormattedStrings FormattedStrings
    {
        get {return new MyFormattedStringsIndexer<string>(this.dict.Values.ToList());}
    }
}

class MyFormattedStringsIndexer<T>
{
    private IList<T> list;  // we take only reference, so there is no overhead

    public MyFormattedStringsCollection (IList<T> list)
    {
        this.list = list;
    }

    // the indexer:
    public T this[int i]
    {
        get
        {
            // this is where the lazy stuff happens:
            // compute the desired element end return it
        }
        set
        {
            // ...
        }
    }
}

Теперь вы можете использовать свой Class следующим образом:

string formattedS = FormattedStrings[5];

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

Подробнее об индексаторах можно прочитать здесь: http://msdn.microsoft.com/en-us/library/6x16t2tx.aspx

person Maciej Sz    schedule 04.12.2013
comment
Спасибо за ваш ответ. Я действительно хотел сохранить словарь, потому что я хочу получить доступ к десятичным значениям для определенной даты. Я не уверен, что ваше решение будет подходящим с точки зрения ремонтопригодности, поскольку не совсем понятно, что даты и десятичные значения связаны друг с другом. - person Yeehaw; 05.12.2013
comment
Извините, я что-то перепутал. Конечно, у меня все еще есть словарь в классе Class... Мне очень нравится ваше решение, оно не создает накладных расходов и сохраняет все в чистоте. Спасибо! (Я бы проголосовал за вас, если бы мог) - person Yeehaw; 05.12.2013

Это VB, но вы поняли...

Public Class Something

  Public Property Items As Dictionary(Of DateTime, String)

  Public Readonly Property FormattedItem(ByVal index As Int32) As String
    ' add error checking/handling as appropriate
    Return Me.Items.Keys(index).ToString("custom format")  '  or whatever your formatting function looks like.
  End Property
End Class
person Sam Axe    schedule 04.12.2013
comment
Спасибо за это, на самом деле я больше искал общее решение для получения подобъекта объекта в методе доступа. Это хорошее решение для форматирования строки, я уверен, что когда-нибудь смогу его использовать. - person Yeehaw; 05.12.2013

Похоже, хороший кандидат в новый класс

public class MyObject
{
    public DateTime Key {get;set;}
    public String Name {get;set;}
    public String FormattedString {get;}
}

И тогда его можно использовать в любом контейнере (List<MyObject>, Dictionary<MyObject> и т. д.).

person Artru    schedule 04.12.2013
comment
Если бы мне нужна была коллекция всех отформатированных строк, мне пришлось бы обращаться к FormattedString для каждого отдельного элемента в моем объекте List<MyObject>. Не добавляет ли это ненужных накладных расходов? - person Yeehaw; 05.12.2013
comment
Никакой разницы в накладных расходах не будет. Если вам нужна коллекция элементов FormattedString, вы все равно будете ее повторять (независимо от того, готовите ли вы ее для индексатора). Будет более понятно использовать принцип ОО. Вместо записи [int index = 1;/*затем применить найденный индекс*/ FormattedStrings[index] вы можете написать myObject.FormattedString]. Просто спросите своего коллегу, что более понятно, и если это требует гораздо больше времени, чтобы понять и использовать ваш код, чем это можно было бы сделать с помощью ОО ---› потратив свою жизнь и деньги компании. - person Artru; 05.12.2013

Ваши методы получения свойств Dates и Strings возвращают новый список при каждом вызове. Поэтому, если вызывающий абонент делает следующее:

Class myClass = ...
for(i=0; i<myClass.Strings.Count; i++)
{
    var s = myClass.Strings[i];
    ...
}

то каждая итерация цикла будет создавать новый список.

Я не понимаю, чего вы действительно пытаетесь достичь здесь. Вы заключаете свойства словаря Keys и Values в ReadOnlyCollections. Это дает вам индексатор, который не имеет большого значения, поскольку порядок ключей в Dictionary<TKey, TValue> не указан.

Переходя (наконец-то!) к вашему вопросу, если вы хотите выполнить форматирование «ленивым» способом, вы можете создать собственный класс, который реализует IList<string> только для чтения и обертывает ваш список ключей (IList<DateTime>). Большая часть реализации является шаблонной, и ваш индексатор выполнит форматирование. Вы также можете кэшировать отформатированные значения, чтобы форматировать только один раз при многократном доступе. Что-то типа:

public class MyFormattingCollection : IList<string>
{
    private IList<decimal> _values;
    private IList<string> _formattedValues;

    public MyFormattingCollection(IList<DateTime> values)
    {
        _values = values;
        _formattedValues = new string[_values.Count];
    }

    public string this[int index]
    {
        get
        {
            var result = _formattedValues[index];
            if (result == null)
            {
                result = FormatTheWayIWant(_values[index]);
                _formattedValues[index] = result;
            }
            return result;
       }
       set
       {
           // Throw: it's a readonly collection
       }
    }

    // Boilerplate implementation of readonly IList<string> ...
}
person Joe    schedule 04.12.2013
comment
К вашему первому замечанию: я думаю, в моем случае было бы лучше использовать SortedDictionary, поскольку я хотел бы, чтобы словарные записи были отсортированы по дате и времени. Своими аксессорами я старался возвращать только те объекты, которые мне действительно нужны. Это означает, что когда я ищу определенную дату в своем словаре, я просто обращаюсь к свойству DateTimes, потому что мне пока не нужно знать о соответствующем значении. - person Yeehaw; 05.12.2013
comment
Вопрос о вашем решении. Вы лениво инициализируете объект по определенному индексу. Но как только он инициализирован, if все время ссылается на один и тот же объект. Что бы вы сделали, если бы базовый словарь изменился, и мне нужно было бы обновить форматированное строковое представление этой записи словаря? - person Yeehaw; 05.12.2013
comment
@Yeehaw, если вы хотите делать что-то ленивым способом, включая списки, которые вы создаете для свойств DateTimes и Values, вам нужно будет повторно инициализировать лениво сгенерированные поля каждый раз, когда вы обновляете словарь. Просто чтобы быть ясным, я бы не рекомендовал это решение. ИМХО, было бы лучше просто позволить вызывающей стороне вызывать метод форматирования при доступе к необработанным значениям. - person Joe; 05.12.2013