【问题标题】:How to get arguments from an Expression where TDelegate is a callback如何从 TDelegate 是回调的表达式中获取参数
【发布时间】:2015-02-23 19:41:43
【问题描述】:

我正在尝试编写一个简单的通用缓存,但在使用 System.Func 作为回调生成足够唯一的键时遇到了问题。

我理想中想要的是能够传递一些描述的可调用委托,以便缓存本身可以获取值,并从同一个表达式中确定一个键。现在我遇到了异常,因为我没有传入实现或继承自 MethodCallExpression 的参数。对于这种预期行为,我应该使用什么来代替 System.Func

public class SimpleCacheKeyGenerator : ICacheKey
{
    public string GetCacheKey<T>(Expression<Func<T>> action)
    {
        var body = (MethodCallExpression) action.Body; //!!! Exception Raised - action.Body is FieldExpression

        ICollection<object> parameters = (from MemberExpression expression in body.Arguments
                                          select
                                              ((FieldInfo) expression.Member).GetValue(
                                                  ((ConstantExpression) expression.Expression).Value)).ToList();

        var sb = new StringBuilder(100);
        sb.Append(body.Type.Namespace);
        sb.Append("-");
        sb.Append(body.Method.Name);

        parameters.ToList().ForEach(x =>
                                        {
                                            sb.Append("-");
                                            sb.Append(x);
                                        });

        return sb.ToString();
    }
}

public class InMemoryCache : ICacheService
{
    private readonly ICachePolicy _cachePolicy;
    private readonly ICacheKey _cacheKey;

    public InMemoryCache(ICachePolicy cachePolicy, ICacheKey cacheKey)
    {
        _cachePolicy = cachePolicy;
        _cacheKey = cacheKey;
    }

    public T Get<T>(Func<T> getItemCallback) where T : class
    {
        var cacheID = _cacheKey.GetCacheKey(() => getItemCallback);
        var item = HttpRuntime.Cache.Get(cacheID) as T;
        if (item == null)
        {
            item = getItemCallback();

            if (_cachePolicy.RenewLeaseOnAccess)
            {
                HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, System.Web.Caching.Cache.NoAbsoluteExpiration, _cachePolicy.ExpiresAfter);
            }
            else
            {
                HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, DateTime.UtcNow + _cachePolicy.ExpiresAfter, System.Web.Caching.Cache.NoSlidingExpiration);
            }
        }

        return item;
    }
} 

【问题讨论】:

  • 表达式的类型
  • 您是否有多个底层方法被 Func 包装用于相同类型的 T 返回值?如果不是,您可以获取 Func 的哈希码(相同的签名通常会产生相同的哈希码)或使用 T 的类型作为您的密钥。否则,它在什么上下文中确定将哪个方法作为 Func 传递?在那种情况下,有什么东西可以提供钥匙吗?
  • 这是对那个问题的回应吗:stackoverflow.com/questions/3766698/… ?

标签: c# linq caching lambda


【解决方案1】:

问题是,如果不复制代码,您不能轻松地同时使用 Expression> 和 Func 来表示同一事物。

您可以使用 LambdaExpression>.Compile() 方法将 Expression> 转换为 Func,但这可能会产生性能问题,因为 Compile 实际上使用程序集发射,这非常昂贵。

这是我如何在不使用表达式和编译的情况下实现相同的东西。 您可以在标准 Linq 扩展中随处找到相同的模式。

将您的论点作为单独的对象传递。 您用作参数的类型将用于委托的类型推断,而参数本身将为委托提供相同类型的参数。

请注意,此实现中的缓存之所以有效,是因为用作参数的匿名对象的默认 ToString 实现。

void Main()
{
    var computeCount = 0;
    var item1 = GetCached(new{x = 1, y = 2}, (arg)=>{computeCount++; return arg.x + arg.y;});
    Console.WriteLine(item1);
    var item2 = GetCached(new{x = 1, y = 2}, (arg)=>{computeCount++; return arg.x + arg.y;});
    Console.WriteLine(item2);
    var item3 = GetCached(new{x = 1, y = 3}, (arg)=>{computeCount++; return arg.x + arg.y;});
    Console.WriteLine(item3);
    Console.WriteLine("Compute count:");
    Console.WriteLine(computeCount);
}
Dictionary<string, object> _cache = new Dictionary<string, object>();
E GetCached<T, E>(T arg, Func<T,E> getter)
{
    // Creating the cache key.
    // Assuming T implements ToString correctly for cache to work.
    var cacheKey = arg.ToString();

    object result;

    if (!_cache.TryGetValue(cacheKey, out result))
    {
        var newItem = getter(arg);
        _cache.Add(cacheKey, newItem);
        return newItem;
    }
    else
    {
        Console.WriteLine("Cache hit: {0}", cacheKey);
    }

    return (E)result;
}

控制台输出:

3
Cache hit: { x = 1, y = 2 }
3
4
Compute count:
2

【讨论】:

    【解决方案2】:

    你得到这个异常是因为(() =&gt; getItemCallback) 表示(() =&gt; { return getItemCallback; })

    这就是为什么action.Body 不是方法调用,而是返回语句。如果您将代码更改为(() =&gt; getItemCallback()),则不应出现该错误。但你不会有任何争论。

    要获取基本调用的参数,您必须更改代码以接受表达式并编译您的 lambda。

    public T Get<T>(Expression<Func<T>> getItemCallbackExpression) where T : class
    {
        var cacheID = _cacheKey.GetCacheKey(getItemCallbackExpression);
        var item = HttpRuntime.Cache.Get(cacheID) as T;
        if (item == null)
        {
            item = getItemCallback.Compile()();
    
            if (_cachePolicy.RenewLeaseOnAccess)
            {
                HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, System.Web.Caching.Cache.NoAbsoluteExpiration, _cachePolicy.ExpiresAfter);
            }
            else
            {
                HttpContext.Current.Cache.Insert(cacheID, getItemCallback, null, DateTime.UtcNow + _cachePolicy.ExpiresAfter, System.Web.Caching.Cache.NoSlidingExpiration);
            }
        }
    
        return item;
    }
    

    我不会推荐这种方法,因为编译表达式需要时间。

    手动生成缓存键可能更容易且性能更高。如果你真的想自动管理缓存键。你可以看看使用 castle.Core 或 PostSharp 的 Aspect Oriented Programmation。这些工具将允许您自动将代码添加到您的某些方法并自动添加缓存逻辑。

    【讨论】:

      【解决方案3】:

      我修改了如下代码,这样得到了预期的结果,所以你可以试试这个,希望对你有帮助。

       public class SimpleCacheKeyGenerator
      {
          public string GetCacheKey<T, TObject>(Expression<Func<T, TObject>> action)
          {
              var body = (MethodCallExpression) action.Body;
              ICollection<object> parameters = body.Arguments.Select(x => ((ConstantExpression) x).Value).ToList();
      
              var sb = new StringBuilder(100);
              sb.Append(body.Type.Namespace);
              sb.Append("-");
              sb.Append(body.Method.Name);
      
              parameters.ToList().ForEach(x =>
              {
                  sb.Append("-");
                  sb.Append(x);
              });
      
              return sb.ToString();
          }
      }
      
      public class InMemoryCache
      {
          public void Get<T, TObject>(Expression<Func<T, TObject>> getItemCallback)
          {
              var generator = new SimpleCacheKeyGenerator();
              Console.WriteLine(generator.GetCacheKey(getItemCallback));
          }
      }
      

      主要:

      private static void Main(string[] args)
          {
              var cache = new InMemoryCache();
      
              var tt = new SomeContextImpl();
              cache.Get<SomeContextImpl, string>(x => x.Any("hello", "hi"));
      
              Console.ReadKey();
          }
      

      somcontextimpl:

       public class SomeContextImpl
      {
          public string Any(string parameter1, string parameter2) { return ""; }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-22
        • 2015-12-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多