Даункст из Generic без потери выразительности - PullRequest
1 голос
/ 02 марта 2011

У меня есть что-то вроде этого:

public class Something
{
    private IDictionary<object,Activity> fCases;

    public IDictionary<object,Activity> Cases
    {
        get { return fCases; }
        set { fCases = value; }
    }
}

public sealed class Something<T> : Something 
{
    private IDictionary<T,Activity> fCases;

    public override IDictionary<T,Activity> Cases
    {
        get { return fCases; }
        set { fCases = value; }
    }
}

Примечание: переопределение не принимается в этом случае

Из-за интенсивного использования Reflection существуют ситуации, когдаЯ должен понизить значение с Something<T> до Something, но, думаю, поскольку свойство Cases скрыто, я теряю данные Cases.

Как я могу обойти эту ситуацию?Я пытался использовать where T:object, но это также не принимается.

РЕДАКТИРОВАТЬ: Это пример того, почему мне нужно наследование:

if (someVar is Something) {

    if (someVar.GetType().IsGenericType) 
    {
        // Construct AnotherObject<T> depending on the Something<T>'s generic argument
        Type typeArg = someVar.GetType().GetGenericArguments()[0],
            genericDefinition = typeof(AnotherObject<>),
            typeToConstruct = genericDefinition.makeGenericType(typeArgs);

        object newAnotherObject = Activator.CreateInstance(typeToConstruct);

        // Pass Something 'Cases' property to AnotherObject<T>
        constructedType.InvokeMember(
            "Cases",
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
            null,
            newActivity,
            new Object[] { someVar.Cases });
    }
}

Но, потому что "Случаи"скрыт, всегда будет нулевымБез наследования мне пришлось бы написать БОЛЬШОЕ if-then-else со всеми возможными общими аргументами.И, поверьте мне, мне действительно нужно использовать someVar is Something и Reflection для создания всех этих объектов.Это большой универсальный API, конвертируемый в другой большой универсальный API, поэтому они не должны знать друг друга, и конвертация должна быть максимально прозрачной.

Ответы [ 4 ]

4 голосов
/ 02 марта 2011

Вы не сможете переопределить это так, и на то есть веская причина.

Представьте себе:

Something x = new Something<string>();
Button key = new Button();
x.Cases[key] = new Activity();

Если ваше переопределение сработало, оно будет пытаться сохранить ссылку Button в качестве ключа в Dictionary<string, Activity>. Это было бы плохо.

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

2 голосов
/ 02 марта 2011

Это категорически не сработает, потому что интерфейс IDictionary<TKey, TValue> инвариантен.IDictionary<object, Activity> нельзя трактовать как IDictionary<T, Activity>.

То, что вы могли бы сделать, вместо того, чтобы выставлять весь IDictionary<T, Activity> в производном классе, просто делегировать вызовы, которые вы хотитечтобы выставить, как это:

public class Something
{
    protected IDictionary<object, Activity> Cases { get; set; }
}

public sealed class Something<T> : Something 
{
    public Activity GetCase(T key)
    {
        return Cases[key];
    }

    public void AddCase(T key, Activity case)
    {
        Cases.Add(key, case);
    }

    // etc. etc.
}

В качестве альтернативы, вы также можете определить свой собственный контравариантный интерфейс, например:

interface IKeyedCollection<in TKey, TValue>
{
    TValue this[TKey key] { get; set; }
    void Add(TKey key, TValue value);
}

Для приведенного выше интерфейса, IKeyedCollection<object, Activity> может действовать как IKeyedCollection<T, Activity>, потому что каждый T является object.

1 голос
/ 30 мая 2011

Во время интенсивного использования рефлексии у меня также возникла необходимость «отвлекаться» от универсальных типов. Я знал, что определенные вызовы будут совместимы, но я не знал типы во время компиляции. Если вы посмотрите на это таким образом, то это на самом деле не «upcasting» универсального типа, а, скорее, , позволяющий использовать generics во время отражения, генерируя правильные downcast .

С этой целью я создал вспомогательный метод для создания делегатов в соответствии с Delegate.CreateDelegate , но позволяющий создавать менее универсальный делегат. Даункасты генерируются там, где это необходимо. Я объясняю это подробно в моем блоге .

MethodInfo methodToCall = typeof( string ).GetMethod( "Compare" );
Func<object, object, int> unknownArgument
    = DelegateHelper.CreateDowncastingDelegate<Func<object, object, int>>(
          null, methodToCall );
unknownArgument( "test", "test" ); // Will return 0.
unknownArgument( "test", 1 ); // Will compile, but throw InvalidCastException.

Чуть позже у меня возникла необходимость создать целые менее универсальные классы-оболочки для универсальных классов, чтобы все вызовы методов сразу стали доступны как менее универсальные вызовы во время отражения. Это может или не может быть полезным в вашем сценарии. Для этого я создал (не так тщательно протестированный) метод, который позволяет генерировать этот класс-обертку во время выполнения с использованием emit. Он доступен в моей библиотеке с открытым исходным кодом . Я еще не писал об этом, поэтому, если вам интересно, вам просто нужно попробовать его (и, возможно, увидеть, что он потерпит неудачу, поскольку он все еще довольно новый).

1 голос
/ 02 марта 2011

Если вы попытаетесь выставить несовместимые типы на разных уровнях, у вас будут возникать проблемы, потому что в конце концов вам придётся поддерживать 2 отдельных объекта (или 1 пользовательский объект с 2 интерфейсами).не может полностью удовлетворить)

Эти типы несовместимы, поскольку есть значения, которые можно добавить к IDictionary<object, Activity>, которые нельзя добавить к каждому экземпляру IDictionary<T, Activity>.Представьте себе, например, T установлен как string, и разработчик использует ключ int в другом месте через Something.Это создает реальную проблему для Something<string> реализаций.

Способ, которым я бы подошел к этому, состоит в том, чтобы изменить базовый тип Something, чтобы не предоставлять конкретный тип, а вместо этого предоставлять соответствующие API.

public abstract class Something {
  public abstract IEnumerable<KeyValuePair> GetElements(); 
  public abstract bool TryGetValue(object key, out Activity value);
}

Это дает Something<T> гибкость, необходимую для правильного подкласса Something и очень выразительную в отношении типов, которые он хочет выставить

public sealed class Something<T> : Something {
  private IDictionary<T,Activity> fCases;

  public override IDictionary<T,Activity> Cases
  {
    get { return fCases; }
    set { fCases = value; }
  }

  public override IEnumerable<KeyValuPair<object, Activity>> GetElements() {
    foreach (var cur in fCases) {
      yield return new KeyValuePair<object, Activity>(cur.Key, cur.Value);
    }
  }

  public override bool TryGetValue(object key, out Activity activity) {
    try {
      T typedKey = (T)key;
      return fCases.TryGetValue(typedKey, out activity);
    } catch (InvalidCastException) {
      activity = null;
      return false;
    }
  }
}

}

...