Как использовать метод скрытия (новый) с общим ограниченным классом - PullRequest
2 голосов
/ 16 апреля 2010

У меня есть контейнерный класс, который имеет универсальный параметр, который ограничен некоторым базовым классом. Тип, предоставленный универсальному, является частью ограничения базового класса. Подкласс использует метод скрытия (новый), чтобы изменить поведение метода от базового класса (нет, я не могу сделать его виртуальным, поскольку это не мой код). Моя проблема в том, что «новые» методы не вызываются, кажется, что компилятор считает предоставленный тип базовым классом, а не подчиненным, как если бы я вывел его на базовый класс.

Очевидно, я здесь неправильно понимаю что-то фундаментальное. Я думал, что универсальный where T: xxx был ограничением, а не типом upcast.

Этот пример кода в основном демонстрирует то, о чем я говорю.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GenericPartialTest
{
    class ContextBase
    {
        public string GetValue()
        {
            return "I am Context Base: " + this.GetType().Name;
        }

        public string GetOtherValue()
        {
            return "I am Context Base: " + this.GetType().Name;
        }

    }

    partial class ContextSub : ContextBase
    {
        public new string GetValue()
        {
            return "I am Context Sub: " + this.GetType().Name;
        }
    }

    partial class ContextSub
    {
        public new string GetOtherValue()
        {
            return "I am Context Sub: " + this.GetType().Name;
        }
    }

    class Container<T> where T: ContextBase, new()
    {
        private T _context = new T();

        public string GetValue()
        {
            return this._context.GetValue();
        }

        public string GetOtherValue()
        {
            return this._context.GetOtherValue();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Simple");
            ContextBase myBase = new ContextBase();
            ContextSub mySub = new ContextSub();

            Console.WriteLine(myBase.GetValue());
            Console.WriteLine(myBase.GetOtherValue());
            Console.WriteLine(mySub.GetValue());
            Console.WriteLine(mySub.GetOtherValue());

            Console.WriteLine("Generic Container");
            Container<ContextBase> myContainerBase = new Container<ContextBase>();
            Container<ContextSub> myContainerSub = new Container<ContextSub>();

            Console.WriteLine(myContainerBase.GetValue());
            Console.WriteLine(myContainerBase.GetOtherValue());
            Console.WriteLine(myContainerSub.GetValue());
            Console.WriteLine(myContainerSub.GetOtherValue());


            Console.ReadKey();
        }
    }
}

Edit:

Полагаю, мое замешательство связано с тем, что это можно сделать

class SomeClass<T> where T: AnotherType, new()
{
    T foo = new T();     
}

И я ожидал, что T будет T, хотя я понимаю, что компилятор будет рассматривать T как имеющий AnotherType интерфейс. Я предполагал, что ввод T произойдет во время выполнения, даже если интерфейс T был установлен во время компиляции. Объявление T foo кажется здесь вводящим в заблуждение, потому что оно действительно делает

AnotherType foo = new T();

Как только я понимаю, что на самом деле foo не объявляется как тип T, становится понятно, почему скрытие метода new не будет работать.

И это все, что я должен сказать об этом.

Ответы [ 3 ]

2 голосов
/ 16 апреля 2010

Объявленные методы new не имеют отношения (с точки зрения компилятора) к методам с одинаковым именем / сигнатурой в базовом классе. Это просто способ компилятора позволить вам определять различные методы впроизводные классы, которые разделяют сигнатуру с методом в их иерархии базового класса.

Теперь, что касается вашего конкретного случая, следует понимать, что генерики должны компилироваться в один набор байт-кода независимо от типов, которые предоставляются в качестве универсальных параметров .В результате компилятор знает только о методе и свойствах, которые определены в универсальном типе T - это будет базовый тип, указанный вами в универсальном ограничении.Компилятор ничего не знает о методах new в вашем производном типе, даже если вы создаете экземпляр универсального типа с производным типом в качестве параметра.Поэтому вызовы в универсальном классе всегда будут идти к методам базового типа.

Существует много путаницы с новым / виртуальным / переопределением; взглянем на этот SO вопрос - ответы Джейсона и Эрика превосходны. Ответ Джона Скита на аналогичный вопрос также может помочь вам понять, почему ваша реализация ведет себя так, как она делает.

Существует два возможных способа решения этой проблемы:

  1. Выполните условное приведение (на основе информации о типе среды выполнения) к производному типу (или интерфейсу) в вашем универсальном классе.Это нарушает инкапсуляцию и добавляет нежелательную связь.Это также хрупко, если реализовано плохо.
  2. Определите интерфейс , который вы используете в своем общем ограничении, которое раскрывает методы, которые вас интересуют.Это может быть невозможно, если код, из которого вы производите, не может быть изменен.
1 голос
/ 16 апреля 2010

Добавьте еще один слой - унаследуйте свой универсальный не от вашего третьего класса, а от нового класса, который, в свою очередь, наследует от третьего лица. В этом новом классе вы можете определить рассматриваемый метод как новый виртуальный. Если весь ваш код никогда не ссылается непосредственно на третий класс, он должен работать

1 голос
/ 16 апреля 2010

Я думаю этот ТАК вопрос Я спросил, может помочь вам. См. Ответ Джона Скита о том, почему это невозможно.

...