Freddy
Freddy

Reputation: 970

Create dictionary with memberexpression as key

I would like to create a cache for my objects and expressions. With the following code he runs true all the elements, after this he would be able to use the expression/object from the dictionary. Instead of doing this he adds the member expression again to the dictionary with the same object.

Is it possible to create a dictionary with a member expression, what am I doing wrong here?

private static readonly IDictionary<MemberExpression, object> Cache = new Dictionary<MemberExpression, object>();
private static Func<TClass, TProperty> GetCachedMemberFunction<TClass, TProperty>(
    Expression<Func<TClass, TProperty>> member)
{
    Func<TClass, TProperty> func;
    var memberExpression = member.Body as MemberExpression;
    if (Cache.ContainsKey(memberExpression))
    {
        func = (Func<TClass, TProperty>)Cache[memberExpression];
    }
    else
    {
        func = member.Compile();
        Cache[memberExpression] = func;
    }

    return func;
}

Note: As you can see from the code I would like to create a cache so I only need to the compile function once.

Upvotes: 1

Views: 517

Answers (1)

Bas
Bas

Reputation: 27105

First off, I would like to say that caching expressions is something that should be done with utmost care and is extremely complex to implement as the example from the comments (this question) already shows. There is a reason why Equals has not been overridden for expressions, because they are so complex to compare.

Some of the main issues with expressions when it comes to comparing them:

  • Comparing expressions is about as expensive as compiling them
  • When do you consider two expressions equal?

E.g.

y => y.MyProperty.AnotherProperty
x => x.MyProperty.AnotherProperty

Are the above two expressions equal? It depends on the use case (if you are analyzing code you might want to take the variable name into account). Also, consider the following example:

class BaseClass { int MyProperty { get; } }
class DerivedClass : BaseClass { } 

Now lets say we have two expressions that are pretty much equal:

BaseClass x => x.MyProperty;
DerivedClass x => x.MyProperty;

Should these be considered equal? Probably not, but maybe in your case.

end of rant

Since you are dealing with MemberExpressions you are probably doing something with property getters and setters or the like. In this case, it might be sufficient to use the Type in combination with the MemberInfo as a key for your Cache object. Note that this only applies when ALL expressions are in the form of x => x.Property without any nested access, casts or whatever. In that case you could come up with something like this:

private struct MemberAccessKey
{
    public readonly Type Type;
    public readonly MemberInfo Member;

    public MemberAccessKey(Type t, MemberInfo m)
    {
        Type = t; Member = m;
    }

    public override bool Equals(object obj)
    {
        if (!(obj is MemberAccessKey)) return false;

        var other = (MemberAccessKey)obj;
        return other.Type == Type && other.Member == Member;
    }

    public override int GetHashCode()
    {
        return Type.GetHashCode() ^ Member.GetHashCode();
    }
}

And then instantiate it based on a MemberExpression:

TypeKey key = new TypeKey(typeof(TClass), memberExpression.Member);

Conclusion

  1. Don't do it because of performance reasons
  2. Don't do it because it only introduces bugs
  3. Don't do it!
  4. If you know that every single expression you create is in the form x => x.MyProperty then you might be able to use this as a key. Note that any other form will either crash or resolve to a duplicate key.

Upvotes: 2

Related Questions