Gegeben sei eine generische Methode GetRepository<TEntity>, die ein Repository für Entitäten des Typs TEntity zurückgibt. Das Repository soll eine stark typisierte Load-Methode haben, also beispielsweise:
repo.Load(42); repo.Load(Guid.Empty);
Der Typ des Parameters von Load soll dabei nicht explizit angegeben werden, um zu verhindern, dass ein Anwender verschiedene Typen angeben kann:
var repo = GetRepository<Person, Guid>(); var repo = GetRepository<Person, int>();
Das Repository für Person soll also immer mit dem gleichen Typ für die ID zurückgegeben werden. Diesen Typen zu ermitteln, ist per Reflection kein Problem. Allerdings ist die Methodensignatur
public IRepository<TEntity, TIdentity> GetRepository<TEntity>();
aus naheliegenden Gründen nicht gültig in C#. Die Idee war nun, einen Lambda-Ausdruck zu übergeben, der die ID-Property spezifiziert:
public IRepository<TEntity, TIdentity> GetRepository<TEntity, TIdentity>(Func<TEntity, TIdentity>);
Der Aufruf sähe dann folgendermaßen aus:
var repo = GetRepository<Person>(p => p.Id);
Leider funktioniert auch das nicht, weil der Compiler nicht in der Lage ist, einen generischen Typparameter aus einem Func herzuleiten.
Wie könnte man dieses Problem elegant lösen, ohne explizit Guid oder int angeben zu müssen?
Folgender Code läßt sich compilieren und in meinen Augen recht gut lesen:
using System;
namespace ReposTest { public class Person { public int Id { get; set; } }
public interface IRepository<TEntity, TIdentity> {}
public class Root { public static IRepository<TEntity, TIdentity> Get<TEntity, TIdentity>(Func<TEntity,TIdentity> ident) { return null; } }
public class RepositoryFor<TEntity> { public static Func<TEntity, TIdentity> UsingIdentity<TIdentity>(Func<TEntity,TIdentity> ident) { return ident; } }
class Program { static void Main(string[] args) { var repos = Root.Get(RepositoryFor<Person>.UsingIdentity(p => p.Id)); } } }
+1 nicht schlecht. Und ich war daran gescheitert zu erfassen was er eigendlich wollte. Ich glaub ich brauch noch ne Aspirin. Wobei, wieviele darf man davon am Tag überhaupt nehmen? BTT: der Code sieht gut aus und man kann ihn gut lesen.
@Golo: Verstehe die Frage nicht!? "Get" liefert den Typ zurück, den du haben wolltest und alle nötigen Infos sind in der Methode verfügbar. "int" wurde nicht explizit angegeben. Die konkrete Implementierung von "Get" überlasse ich dir. ;-) Du kannst aber "return null" problemlos durch "return new MyRepository<TEntity,TIdentity>(...)" oder sowas ersetzen.
Nachtrag: Die Signatur von Get ist natürlich nicht sooo schön und intuitiv. Ich würde ja sowas wie IRepositorySelector<TEntity,TIdentity> einführen. UsingIdentity würde dann Func<TEntity,TIdentity> erwarten, aber IRepositorySelector<TEntity,TIdentity> zurück liefern. In der Realität macht es bestimmt auch Sinn, teile von Root in die Implementierung von IRespository<> auszulagern. Ich mag es eben sehr, wenn Lösungen quasi zwangsweise besser entkoppelten Code nach sich ziehen. ;-)
var repos = GetRepository(Identity<Person>.Is(p => p.Id));