Simulating Polymorphic/Generic Enumerations - C#
.net, c#, enums, generics, polymorphism
Solution
Eric Lippert will come to my house and beat me with a stick for this, but it works.
Usage:
// finds fruits of type Orange, includes Color and OrangeFamilies
var result = FindFruit(OrangeAssociation.Color,
OrangeAssociation.OrangeFamilies);
or
// finds fruits of type Fruit, includes Manufacturers
var result = FindFruit(FruitAssociation.Manufacturers);
Implementation:
static ICollection<TFruit> FindFruit<TAssoc, TFruit>(
params FruitAssociation<TAssoc, TFruit>[] assocs)
where TAssoc : FruitAssociation<TAssoc, TFruit>, new()
where TFruit : Fruit
{
var query = _fruitRepository.Fruits.OfType<TFruit>();
foreach (var assoc in assocs)
{
query = query.Include(assoc.Name);
}
return query.ToList();
}
with
abstract class FruitAssociation<TAssoc, TFruit>
where TAssoc : FruitAssociation<TAssoc, TFruit>, new()
where TFruit : Fruit
{
public static readonly TAssoc Color = Define("Color");
public static readonly TAssoc Manufacturers = Define("Manufacturers");
protected static TAssoc Define(string name)
{
return new TAssoc { Name = name };
}
public string Name
{
get;
private set;
}
}
sealed class FruitAssociation : FruitAssociation<FruitAssociation, Fruit>
{
}
sealed class OrangeAssociation : FruitAssociation<OrangeAssociation, Orange>
{
public static readonly OrangeAssociation OrangeFamilies =
Define("OrangeFamilies");
}
Problem
I know it's a bizarre title, and i know inheritance is not possible with enums, so let me explain. If i have the following classes: - Fruit (abstract) - Apple (concrete - derived from Fruit) - Orange (concrete - derived from Fruit) And i have the following method, implemented with generics: ``` public ICollection<T> FindFruit<T>() where T : Fruit { return _fruitRepository .Fruits .OfType<T>() .ToList(); } ``` And i use it like this: ``` var apples = _fruitServices.FindFruit<Apple>(); ``` All fine. Now - i currently have the following enum: ``` public enum FruitAssociations { Color, Manufacturers } ``` Basically a "Fruit" can have many associations, which designates what associations to include in the result from my repository. So the `FindFruit` method above is actually like this: ``` public ICollection<T> FindFruit<T>(FruitAssociations[] assocs) where T : Fruit { return _fruitRepository .Fruits .OfType<T>() .IncludeAssocs(assocs) // ObjectQuery<Fruit> extension method. .ToList(); } ``` Here is that extension method: ``` public static ObjectQuery<Fruit> IncludeAssocs(this ObjectQuery<Fruit> source, FruitAssociations[] assocs) { if (assocs.Contains(FruitAssociations.Color)) query = query.Include("Color"); // etc } ``` Also works fine. The problem i am now facing however is i have associations on particular Fruit. E.g ``` public enum OrangeAssociations { OrangeFamilies } ``` And i'm not sure how to have a single `FindFruit` method that is capable of returning associations based on the type of T. This is the end result i would like to be able to do: ``` var oranges = _fruitServices.FindFruit<Orange>(new[] { OrangeAssociations.OrangeFamilies }); var apples = _fruitServices.FindFruit<Apple>(new[] { AppleAssociations.AppleFamilies }); ``` But i can't figure out how to do that without having seperate `FindFruit` methods for each fruit type, and hence defeating the point of the generic T type parameter. For those who are curious as to what i'm doing here - i'm using eager loading with Entity Framework 4, and therefore using the `.Include` method (which takes a string designating the navigational property). So my service accepts an array of enumerations for a given entity, then i use an extension method to translate that to a string used in the `.Include` statement. I'm thinking the solution is to instead of accepting an array of enums, accept a generic class: ``` public ICollection<T> FindFruit<T>(FruitAssociations<T> assocs) where T : Fruit ``` And use it like this: ``` var associations = new FruitAssociations<Orange>(OrangeAssociations.OrangeFamilies); var apples = _fruitServices.FindFruit<Apple>(associations); ``` But i'm not sure how that would work, or how to implement it. Hopefully my question makes sense and is not too long/complicated.