Search code examples
c#generics

How to get a static property from a generic argument in C#?


My goal is to have an abstract class representing one of many DAO objects, any of which can be put into an internal cache indexed by a cache key. The actual processing is done in a separate processor class which takes in the specific DAO class as a generic argument. I am trying to figure out how to use the generic argument to determine the correct cache key

what I'm attempting is basically this

abstract class Dao {
  // some kind of static string called "CacheKeyStem". The below doesn't work because you can't have abstract static properties
  abstract static string CacheKeyStem { get; }

  int Id { get; set; }

  string CacheKey => $"{CacheKeyStem}-{Id}";
}

PersonDao : Dao {
  // override CacheKeyStem. Again, this doesn't work
  public static override string CacheKeyStem => "Person";
}

CarDao : Dao {
  // override CacheKeyStem. Again, this doesn't work
  public static override string CacheKeyStem => "Car";
}

So now we have 2 DAOs, one for persons and one for cars. In my processor class, I have

abstract class BaseProcessor<T> where T : Dao {
  public void AddToCache(T obj){
    _myCache.Add(obj.CacheKey, obj);
  }

  public T RetrieveFromCache(int id){
    // the below obviously doesnt work either.
    return _myCache.Get<T>($"{T.CacheKeyStem}-{id}"); 
  }
}

I'm sure there's a better way to accomplish what I'm trying to do here, but I'm not sure what it is. Within RetrieveFromCache, we know the type of the object we want, but we don't have an instance. We know that the type extends Dao. I need a way to get the CacheKeyStem based on the type of the generic argument.


Solution

  • You can do it with static interface methods available from C#11 and .NET 7:

    For your case, something like this:

    interface IHaveCacheKeyStem {
        abstract static string CacheKeyStem { get; }
    }
    
    abstract class Dao {
        public int Id { get; set; }
        public string CacheKey => $"{GetCacheKeyStem()}-{Id}";
        protected abstract string GetCacheKeyStem();
    }
    
    class PersonDao : Dao, IHaveCacheKeyStem {
        public static string CacheKeyStem => "Person";
        protected override string GetCacheKeyStem() => CacheKeyStem;
    }
    
    class CarDao : Dao, IHaveCacheKeyStem {
        public static string CacheKeyStem => "Car";
        protected override string GetCacheKeyStem() => CacheKeyStem;
    }
    
    abstract class BaseProcessor<T> where T : Dao, IHaveCacheKeyStem {
        // example cache
        Dictionary<string, T> _myCache = new();
        public void AddToCache(T obj) {
            _myCache.Add(obj.CacheKey, obj);
        }
    
        public T RetrieveFromCache(int id) {
            // T.CacheKeyStem would work
            // dictionary doesn't have Get<T> method though
            // so won't compile
            return _myCache.Get<T>($"{T.CacheKeyStem}-{id}");
        }
    }
    

    example use:

    var personDao = new PersonDao() { Id = 42 };
    Console.WriteLine(personDao.CacheKey); // Person-42
    var carDao = new CarDao() { Id = 42 };
    Console.WriteLine(carDao.CacheKey); // Car-42