Tim
Tim

Reputation: 534

Create hash of a class signature for caching

Edit: all answers below (as at 19th Dec '16) are useful in making a decision. I accepted the most thorough answer to my question; but in the end chose to simply hash the file.

I am caching objects and using the assembly version as part of the key to invalidate the cached objects every time the build changes. This is inefficient because the actual class of the cached objects rarely change and are valid across builds.

How can I instead use a hash of the specific class signature (basically all properties) for the key, such that it only changes when the class itself changes?

I can think of a somewhat complicated way using reflection, but I wonder if there is a simple trick I'm missing or any compile time mechanism.

Thanks!

E.g. Signature of Foo --> #ABCD

public class Foo {
    public string Bar {get; set;}
}

New signature of Foo (property type changed) --> #WXYZ

public class Foo {
    public char[] Bar {get; set;}
}

Upvotes: 0

Views: 191

Answers (4)

Noel Widmer
Noel Widmer

Reputation: 4572

As others have pointed out it is dangerous to do something like that because a signature doesn't define the logic behind it. That being sad:

This is an extensible approach:

The method basically uses reflection to crawl through all properties of your type.
It then gets some specific values of those properties and calls ToString() on them.
Those values are appended to a string and GetHashCode() will be used on that string.

private int GetTypeHash<T>()
{
    var propertiesToCheck = typeof(T).GetProperties();

    if(propertiesToCheck == null || propertiesToCheck.Length == 0)
        return 0;

    StringBuilder sb = new StringBuilder();

    foreach(var propertyToCheck in propertiesToCheck)
    {
        //Some simple things that could change:
        sb.Append((int)propertyToCheck.Attributes);
        sb.Append(propertyToCheck.CanRead);
        sb.Append(propertyToCheck.CanWrite);
        sb.Append(propertyToCheck.IsSpecialName);
        sb.Append(propertyToCheck.Name);
        sb.Append(propertyToCheck.PropertyType.AssemblyQualifiedName);

        //It might be an index property
        var indexParams = propertyToCheck.GetIndexParameters();
        if(indexParams != null && indexParams.Length != 0)
        {
            sb.Append(indexParams.Length);
        }

        //It might have custom attributes
        var customAttributes = propertyToCheck.CustomAttributes;
        if(customAttributes != null)
        {
            foreach(var cusAttr in customAttributes)
            {
                sb.Append(cusAttr.GetType().AssemblyQualifiedName);
            }
        }
    }

    return sb.ToString().GetHashCode();
}

Upvotes: 1

JuanR
JuanR

Reputation: 7783

Doing something like this is dangerous as you (or someone else) could be introducing logic into the properties themselves at some point. It's also possible that the properties make internal calls to other methods that do change (among other things). You won't be detecting changes that go beyond the signature so you are leaving the door open to disaster.

If these group of classes you refer to rarely change, consider moving them out of the main assembly and into their own one or even break it down into more than one assembly if it makes sense. That way their assembly(ies) will not change versions and there will be no cache refresh.

Upvotes: 1

Xiaoy312
Xiaoy312

Reputation: 14477

You can use the public properties of the class and generate an hash based on the name and type of each property:

int ComputeTypeHash<T>()
{
    return typeof(T).GetProperties()
        .SelectMany(p => new[] { p.Name.GetHashCode(), p.PropertyType.GetHashCode() })
        .Aggregate(17, (h, x) => unchecked(h * 23 + x));
}

ComputeTypeHash<Foo_v1>().Dump(); // 1946663838
ComputeTypeHash<Foo_v2>().Dump(); // 1946663838
ComputeTypeHash<Foo_v3>().Dump(); // 1985957629

public class Foo_v1
{
    public string Bar { get; set; }
}
public class Foo_v2
{
    public string Bar { get; set; }
}

public class Foo_v3
{
    public char[] Bar { get; set; }
}

Upvotes: 1

Dan Hunex
Dan Hunex

Reputation: 5318

You can hash the whole class file and use that as a key. When the file changes, the hash will change and that will meet your need

Upvotes: 1

Related Questions