【问题标题】:Can you avoid unboxing in a memoizing generic method?你能避免在记忆通用方法中拆箱吗?
【发布时间】:2011-01-19 01:18:10
【问题描述】:

我有一个通用方法,用于将数据库中的字符串值转换为实际转换值。

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

问题是在初始化期间我不知道数据库中的数据是什么类型,只有在第一次调用时我才能做出这个决定。

有没有什么方法可以构造它,使其不会被强制拆箱缓存的值类型? (不改变包含类的签名)

【问题讨论】:

  • 如果不将DoSomethingExpensive 设为通用,我真的看不到任何方法。
  • 我想我可以编写一个动作并存储它......但我敢打赌,调用它比拆箱更昂贵
  • 您发布的代码不一定显示任何(取消)装箱。您能否提供更多上下文(和代码)来显示包含类中发生了什么?
  • @LukeH ... 添加了缺失的变量。
  • 这段代码对我来说没有任何意义。 “rawVal”从何而来? DoSomethingExpensive 如何知道它应该为某个任意 T 返回一个 T?

标签: c# generics boxing memoization


【解决方案1】:

您提供的代码有些奇怪。 “DoSomethingExpensive”如何知道为任意 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();

你就完成了。不需要拳击。

【讨论】:

  • Eric ...我同意这有点奇怪...但它对我们来说有一个疯狂的小用例:在我们的代码中是(T)Convert.ChangeType(DatabaseValue, typeof(T))
  • @Sam:我认为如果你提出你实际遇到的问题而不是试图制作一个奇怪的抽象版本,这个问题会更有意义。
【解决方案2】:

如果 T 和 Convert.ChangeType 的结果是引用类型,则不会拆箱。

如果Convert.ChangeType 返回一个装箱的值类型,而 T 是一个值类型,那么如果你想让 GetValue 返回 T,你就无法避免拆箱。

【讨论】:

    【解决方案3】:

    考虑为此使用Lazy&lt;T&gt;

    因此,如果您有某种属性集合。

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

    【讨论】:

    • 如果 val 和 valSet 是私有的,则不会。你不必暴露 Lazy,你可以在内部使用它。
    • 这种休息对我来说,我需要能够把 MyClass 放在一个字典中......在知道它最终将包含的类型之前......
    • @Sam - 当我写这篇文章时,我没有 100% 遇到最初的问题 - 我看到了你的困境。我假设 SomeLongRunning 家伙将仅依赖于传入的属性名称,而不依赖于先前获取的数据。这为您留下了 2 个字典来以这种方式或您以前的方式实现它 - 这更清洁。注意,您可以摆脱您的 valSet 私有并用检查您的 val 容器是否为空来替换它 - 因为在 null 实际值的情况下,您将有一个实际容器持有 null :)
    【解决方案4】:

    我愿意打赌(好吧,我也有源代码的优势......)在大多数情况下,这个代码是根据你在your reply中的示例调用的

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

    然后将&lt;T&gt; 代码提升一个级别,这样就可以在强制转换中完成。

    【讨论】:

      【解决方案5】:

      这使得值类型的东西稍微快一点(没有拆箱)......而对于 ref 类型的额外调用则稍微慢一点......但是有点奇怪。

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

      【讨论】:

      • 这当然是一段相当脆弱的代码。如果您先调用 GetValue(),然后再调用 GetValue(),则会出现异常。当然,在这种情况下很难知道这是否是一个问题。
      • @Neil,就我的情况而言:如果有人这样做:Get(setting: "Site.Twitter.AccountName", default:""); 后跟 Get(setting: "Site.Twitter.AccountName", 77); ...我真的想要东西爆炸
      • 当然 - 听起来很合理。然后在那种情况下,虽然整个 MyClass 可能是通用的。我将编辑我的答案以反映。
      • 在我的回答中查看添加的代码 - 我把它放在那里是因为它更容易谈论代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-04
      • 2023-02-24
      相关资源
      最近更新 更多