Можете ли вы избежать распаковки в запоминающемся универсальном методе? - PullRequest
2 голосов
/ 19 января 2011

У меня есть универсальный метод, который я использую, чтобы запоминать преобразования строковых значений в БД в фактическое преобразованное значение.

public MySpecialValue {
    object val;
    bool valSet = false; 

    T GetValue<T> () { 
         if (!valSet)
         {
                val = (T)Convert.ChangeType(DatabaseValue, typeof(T));
                valSet = true;
         }
         return (T)val;
     }

     public string DatabaseValue { get; set; }
}

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

Есть ли способ структурировать его таким образом, чтобы он не был вынужден распаковывать типы значений, которые кэшируются?(без изменения сигнатуры вмещающего класса)

Ответы [ 5 ]

5 голосов
/ 19 января 2011

Код, который вы даете, несколько странный.Как «DoSomethingExорого» знает, как вернуть T для произвольного T ?Это не имеет смысла для меня.

То, как вы обычно пишете универсальный памятник, выглядит так:

public static Func<T> Memoize(this Func<T> func)
{
    bool executed = false;
    T result = default(T);
    Func<T> memoized = ()=> 
    {
        if (!executed)
        {
            result = func();
            executed = true;
        }
        return result;
    };
    return memoized;
}

И теперь вы можете сказать:

Func<int> expensive = DoSomethingExpensiveThatGetsAnInt;
Func<int> memoized = expensive.Memoize();

Ивсе готовоБокс не требуется.

3 голосов
/ 19 января 2011

Если T и результат Convert.ChangeType являются ссылочными типами, то распаковка не будет.

Если Convert.ChangeType возвращает тип значения в штучной упаковке, а T является типом значения, то вы ничего не можете сделать, чтобы избежать распаковки, если вы хотите, чтобы GetValue возвращала T.

1 голос
/ 19 января 2011

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

Get("Site.Twitter.AccountName", "")

или

Get("Site.Twitter.AccountName", 77)

В этом случае вы используете вывод общего типа. Но есть другая, более простая вещь, которая могла бы скомпилироваться там ... не использовать обобщенные значения . Я ожидаю, что здесь есть только несколько сценариев; поэтому напишите несколько классов / перегрузок методов - один для string, один для int и т. д.

string Get(string key, string defaultValue) {...}
int Get(string key, int defaultValue) {...}
bool Get(string key, bool defaultValue) {...}

Конечно, будет небольшое дублирование, но компилятор сможет оптимизировать для каждого отдельного сценария - без бокса. Вы даже можете (если хотите) заменить Convert.ChangeType на что-то вроде int.Parse (для случая T = int).


Другой вариант (учитывая ваши примеры) - сделать запоминаемый объект универсальным:

public MySpecialValue<T> {
    T val;
    bool valSet = false; 

    T GetValue() { 
         if (!valSet)
         {
                val = (T)Convert.ChangeType(DatabaseValue, typeof(T));
                valSet = true;
         }
         return val;
     }

     public string DatabaseValue { get; set; }
}

и увеличьте код <T> до уровня, чтобы это было сделано в приведении.

1 голос
/ 19 января 2011

Рассмотрите возможность использования Lazy<T> для этого.

Так что если у вас есть какая-то коллекция свойств.

public class MyClass 
{
...
}

Public class MyClass<T> : MyClass
{
  T val;
  bool valSet; 
  public T GetValue<T> () { 
        if (!valSet)
        {
            val = (T)Convert.ChangeType(DatabaseValue, typeof(T))};
            valSet = true;
        }
        return val;
    }
}

Предположительно, у вас есть общий метод в родительском классе

class SomePropertyBag{

private Dictionary<string, MyClass> dict;

T GetValue<T>(string name, T default)
{
  MyClass res;
  if(!dict.TryGetValue(out res))
  {
     res = new MyClass<T>(name);
     dict.Add(name, res);
  }
  return ((MyClass<T>)res).GetValue();
}
0 голосов
/ 19 января 2011

Это делает вещи немного более быстрыми для типов значений (без распаковки) ... и немного медленнее для типов ссылок одним дополнительным вызовом ... однако это немного странно.

 class MyClass {
    class Container<T>
    {
        public T Value { get; set; }
    }

    bool valSet;
    object val; 

    public T GetValue<T> () { 
        if (!valSet)
        {
            val = new Container<T>{Value =  (T)Convert.ChangeType(DatabaseValue, typeof(T))};
            valSet = true;
        }
        return ((Container<T>)val).Value;
    }

    public string DatabaseValue { get; set; }
 }
...