如何从一个表达式获取参数,其中TDelegate是一个回调

时间:2015-02-13 16:54:16

标签: c# linq caching lambda

我正在尝试编写一个简单的通用缓存,但遇到使用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;
    }
} 

3 个答案:

答案 0 :(得分:1)

问题是,你不能轻易使用Expression&gt;和Func表示相同的事情而不重复代码。

你可以转换Expression&gt;使用LambdaExpression&gt; .Compile()方法的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

答案 1 :(得分:0)

您收到此异常,因为(() => getItemCallback)表示(() => { return getItemCallback; })

这就是action.Body不是方法调用的原因,它是return语句。如果您将代码更改为(() => 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查看面向方面的编程。这些工具将允许您自动为某些方法添加代码并自动添加缓存逻辑。

答案 2 :(得分:0)

我修改了下面的代码,我以这种方式得到了预期的结果,所以你可以尝试一下,我希望这会有所帮助。

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