-3

I would like to save in Cache an object with a constant Key.

    public TItem Set<TItem>(string key, TItem value)
    {
        return = _memoryCache.Set(key, value);
    }

I get my object with a function like:

    public TItem Get(
        TPrimaryKey id,
        params object[] args,
        bool byPassCaching = false
    )
    {
        if (byPassCaching || !_cacheManager.TryGetValue<TItem>(GetKey(id, args), out TItem result)){
           item = [FUNCTION TO GET ITEM];
           _cacheManager.Set(GetKey(id, args), item)
        }
        return item;
    }

I would like to generate a constant key for a TItem, Id (int) and some params (object[] args).

Here is my function to generate the constant key:

    internal static string GetKey<TItem>(int id, params object[] args)
    {
        return string.Format("{0}#{1}[{2}]", typeof(TItem).FullName, id, args?[FUNCTION TO CALL]);
    }

[FUNCTION TO CALL] is the function I m looking for.

So next time I call the function with the same parameter, I will have the same key.

For exemple

GetKey<MyClass>(1, null) => "MyApp.MyClass#1[]"
GetKey<MyClass>(1, "Hello", "World") => "MyApp.MyClass#1[sdas5d1as5d4sd8]"
GetKey<MyClass>(1, "Hello", "World") => "MyApp.MyClass#1[sdas5d1as5d4sd8]"
GetKey<MyClass>(1, item => item.Include(s => s.Categories)) => "MyApp.MyClass#1[asdasdasd87wqw]"

At the beginning, I thought to use GetHashCode, but the int generated is always different.

How can I do that?

Cedric Arnould
  • 1,991
  • 4
  • 29
  • 49
  • 1
    How does `IEntity` fit into any of this? – itsme86 Mar 13 '18 at 20:05
  • Explain with example, so we can help. – M.Hassan Mar 13 '18 at 20:17
  • Thanks, I removed the useless IEntity and focus only on the KeyGeneration + I added some exemple. – Cedric Arnould Mar 13 '18 at 20:42
  • This feels like a XY Problem (https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). **Why** do you want to do this? – mjwills Mar 13 '18 at 20:45
  • It really would help if you explained what you will be using these “constant key”s for. – Dour High Arch Mar 13 '18 at 20:45
  • Is it better? For me, the important part is how to generate the Key. I would like to avoid comments like, in this context, you could do something else. I m really instered to know if generate a constant for args is possible. Maybe in this context, it s not necessary, but there is maybe a context where it will. Do you understand my point? – Cedric Arnould Mar 13 '18 at 21:05
  • You pass args array as {2} which is not valid and what is meant by the argument [function ..]. – M.Hassan Mar 13 '18 at 21:07
  • This Is what I m looking for, do you have an idea? – Cedric Arnould Mar 13 '18 at 21:08
  • Strangely, I updated my post following your suggestions, but my note is still -2. Is there still something I could do to help you to understand my question? I just created my account few weeks ago, so a -2 is important on my note. Thanks for your comprehension – Cedric Arnould Mar 13 '18 at 21:34
  • What you're doing is called "memoization", and it is hard to do memoization unless you know the characteristics of the function being memoized. For example, consider this function: `bool M(string s) { return ReferenceEquals(s, ""); }` (Ignore the fact that it would be silly to memoize this function.) A memoizer is a `Dictionary` where the keys of the dictionary are strings and if the string is **reference equal** to `""` then the value is true, false otherwise. Can you say how to memoize that function? – Eric Lippert Mar 13 '18 at 22:30
  • Because whatever technique you use to memoize that function isn't going to work to memoize `bool M(string s) { return s == ""; }` because that uses *value* semantics, not *reference* semantics. **Unless we know whether the memoized function uses value semantics or reference semantics we cannot write a correct memoizer**. This is just one small example of how you must know things about the function being memozied in order to write a correct memoizer. But you are asking how to write a *general purpose* memoizer. That doesn't exist, as I've demonstrated. – Eric Lippert Mar 13 '18 at 22:32

1 Answers1

0

I think GetHashCode is what you need; were you calling GetHashCode on the array, or calling it on each member of the array? You want to do the latter.

The following should be similar to what you want; if the params is null or empty, or full of null objects, then the code will be 00000000.

Otherwise, if the params are the same, then the resulting hash will be the same. And if the params are in a different order or different values, then the hash will be different.

internal static string GetKey<TEntity>(int id, params object[] args)
{
    return string.Format("{0}#{1}[{2}]", typeof(TEntity).FullName, id, ArrayHash(args));
}

static string ArrayHash(params object[] values)
{
    return BitConverter.ToString(BitConverter.GetBytes(ArrayHashCode(values))).Replace("-", "").ToLowerInvariant();
}

static int ArrayHashCode(params object[] values)
{
    if (values == null || values.Length == 0) return 0;
    var value = values[0];
    int hashCode = value == null ? 0 : value.GetHashCode();
    for (int i = 1; i < values.Length; i++)
    {
        value = values[i];
        unchecked
        {
            hashCode = (hashCode * 397) ^ (value == null ? 0 : value.GetHashCode());
        }
    }
    return hashCode;
}

Note that 397 is a prime number that I picked up from ReSharper's GetHashCode implementation, allowing a nice distribution of hash codes through the overflow (gracefully allowed with the unchecked block).

An example of the hashed params would be like so:

ArrayHash( 1, null, "test" )     => "dee9e1ea"
ArrayHash( 1, null, "test" )     => "dee9e1ea"
ArrayHash( null, 1, "test" )     => "fa8fe3ea"
ArrayHash("one", "two", "three") => "8841b2be"
ArrayHash()                      => "00000000"
ArrayHash(null)                  => "00000000"
ArrayHash(new object[] {})       => "00000000"

Note that if the params array contains objects, they need to have a proper GetHashCode implementation too.

David Moore
  • 2,466
  • 22
  • 13
  • Thanks for your comment, is there another way to do it without having to override the GetHasCode? – Cedric Arnould Mar 13 '18 at 21:20
  • If you're passing entities as params, then they should implement GetHashCode. Otherwise, you could write a method that can generate a hash code from the entitie's properties, but that would involve reflection and performance may start to be a problem. Is there any particular reason you're avoiding GetHashCode? – David Moore Mar 13 '18 at 21:24
  • the args can be many things, a func to filter, a func to include data. At the end, I would to create a CacheRepository and CacheUnitOfWork. https://github.com/ranouf/AspNetCore-Angular-AllInOne/tree/Caching/src/common/AllInOne.Common/Repositories/Cache https://github.com/ranouf/AspNetCore-Angular-AllInOne/tree/Caching/src/common/AllInOne.Common/UnitOWork/Cache I know, the idea is not perfect, But I m more interested by the challenge to do it. – Cedric Arnould Mar 13 '18 at 21:31
  • Just change my code then, use GetHashCode for value types, and calculate a hash code for funcs. I don't know if you want to invoke the functions and hash the results, or identify the functions. A lot is missing. – David Moore Mar 13 '18 at 21:48
  • This could be an issue if the in-built types `GetHashCode` implementations change in future - https://stackoverflow.com/questions/7859359/is-gethashcode-guaranteed-to-be-the-same-across-systems-platform-versions . – mjwills Mar 14 '18 at 13:06