Имитация полиморфных / общих перечислений - C # - PullRequest
3 голосов
/ 09 декабря 2010

Я знаю, что это странное название, и я знаю, что наследование невозможно с перечислениями, поэтому позвольте мне объяснить.

Если у меня есть следующие классы:

  • Fruit (аннотация)
    • Яблоко (бетон - полученный из фруктов)
    • Апельсин (бетон - полученный из фруктов)

И у меня есть следующий метод, реализованный с использованием обобщений:

public ICollection<T> FindFruit<T>() where T : Fruit
{
   return _fruitRepository
             .Fruits
             .OfType<T>()
             .ToList();
}

И я использую это так:

var apples = _fruitServices.FindFruit<Apple>();

Все хорошо.

Теперь - в настоящее время у меня есть следующее перечисление:

public enum FruitAssociations
{
   Color,
   Manufacturers
}

По сути, у "Fruit" может быть много ассоциаций, которые обозначают, какие ассоциации включить в результат из моего репозитория.

Таким образом, приведенный выше метод FindFruit на самом деле выглядит следующим образом:

public ICollection<T> FindFruit<T>(FruitAssociations[] assocs) where T : Fruit
{
   return _fruitRepository
             .Fruits
             .OfType<T>()
             .IncludeAssocs(assocs) // ObjectQuery<Fruit> extension method.
             .ToList();
}

Вот этот метод расширения:

public static ObjectQuery<Fruit> IncludeAssocs(this ObjectQuery<Fruit> source, FruitAssociations[] assocs)
{
   if (assocs.Contains(FruitAssociations.Color))
      query = query.Include("Color");
   // etc      
}

Также отлично работает.Однако проблема, с которой я сейчас сталкиваюсь, заключается в том, что у меня есть ассоциации по конкретным фруктам.

Например

public enum OrangeAssociations
{
   OrangeFamilies
}

И я не уверен, как получить один FindFruit метод, который способен возвращать ассоциации на основе типа T.

Это конечный результат, который я хотел бы сделать:

var oranges = _fruitServices.FindFruit<Orange>(new[] { OrangeAssociations.OrangeFamilies });
var apples = _fruitServices.FindFruit<Apple>(new[] { AppleAssociations.AppleFamilies });

Но я не могу понять, как это сделать, не имея отдельных FindFruit методов для каждого типа фруктов и, следовательно, не преодолев точку общего параметра типа T.

Для тех, ктомне любопытно, что я здесь делаю - я использую энергичную загрузку с Entity Framework 4 и, следовательно, использую метод .Include (который принимает строку, обозначающую навигационное свойство).Так что мой сервис принимает массив перечислений для данной сущности, затем я использую метод расширения, чтобы перевести его в строку, используемую в операторе .Include.

Я думаю, что решение состоит в том, чтобы вместо принятиямассив перечислений, принять универсальный класс:

public ICollection<T> FindFruit<T>(FruitAssociations<T> assocs) where T : Fruit

И использовать его так:

var associations = new FruitAssociations<Orange>(OrangeAssociations.OrangeFamilies);
var apples = _fruitServices.FindFruit<Apple>(associations);

Но я не уверен, как это будет работать, или как его реализовать.

Надеюсь, мой вопрос имеет смысл и не слишком длинный / сложный.

Ответы [ 3 ]

4 голосов
/ 09 декабря 2010

Эрик Липперт придет ко мне домой и избьет меня палкой за это, но это работает.

Использование:

// finds fruits of type Orange, includes Color and OrangeFamilies
var result = FindFruit(OrangeAssociation.Color,
                       OrangeAssociation.OrangeFamilies);

или

// finds fruits of type Fruit, includes Manufacturers
var result = FindFruit(FruitAssociation.Manufacturers);

Реализация:

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();
}

с

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");
}
2 голосов
/ 09 декабря 2010

Вы сталкиваетесь с ситуацией, когда Enum и Generics не всегда играют хорошо. Тем не менее, вы можете сделать что-то вроде:

public static ObjectQuery<Fruit> IncludeAssocs(
    this ObjectQuery<Fruit> source, Enum[] assocs) 
{
    foreach(var assoc in assocs)
    {
        query = query.Include(assoc.ToAssociationQueryString());
    }
}

В этом случае я предлагаю определить новый атрибут, например AssociationQueryString, и применить его к перечислениям следующим образом:

public enum OrangeAssociations
{
    [AssociationQueryString("Orange Families")]
    OrangeFamilies
}

Вот пользовательский атрибут:

public class AssociationQueryStringAttribute : System.Attribute
{
    private readonly string value;
    public string Value { get { return this.value; } }

    public AssociationQueryStringAttribute(string value)
    {
        this.value = value;
    }
}

Вот метод расширения .ToAssociationQueryString(), который возвращает значение атрибута:

public string ToAssociationQueryString(this Enum value)
{
    FieldInfo fi = value.GetType().GetField(value.ToString());
    AssociationQueryStringAttribute[] attributes = 
        (AssociationQueryStringAttribute[])fi.GetCustomAttributes(
            typeof(AssociationQueryStringAttribute), false);
    if (attributes.Length > 0)
    {
        return attributes[0].Value;
    }
    else
    {
        throw new InvalidOperationException(
            "Must use an enum decorated with the AssociationQueryString attribute.");
    }
}
0 голосов
/ 09 декабря 2010

Перечисления не так волшебны, как все предполагают. Это просто тип с кучей статических полей только для чтения и закрытым конструктором. Если вы хотите наследования, используйте обычный класс и общедоступные статические поля или свойства, которые возвращают экземпляры класса с нужными значениями.

Пример из другого вопроса

...