Модификатор "new" заставляет базовые реализации иметь значения свойств NULL - PullRequest
1 голос
/ 05 июня 2011

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

// setup output to demonstrate the scenario
static void Main(string[] args)
{
    var foo = new Foo();            
    var foobase = foo as FooBase;

    Console.WriteLine("Foo is null? {0}", foo == null);
    Console.WriteLine("Foo.Bar is null? {0}", foo.Bar == null);
    Console.WriteLine("FooBase is null? {0}", foobase == null);
    Console.WriteLine("FooBase.Bar is null? {0}", foobase.Bar == null);
    Console.ReadLine();            
}

// base and derived.  These represent my problem.
class BarBase { }
class Bar : BarBase { }

// Base implementation using base properties
class FooBase
{
    public virtual BarBase Bar { get; set; }
    public FooBase() { }
}

// Derived implementation using "new" to operate with the 
// derived the property
class Foo : FooBase
{
    public new Bar Bar { get; set; }
    public Foo() : base()
    {
        // populate our modified property with the derived class
        Bar = new Bar();
        //base.Bar = Bar; // I don't want to do this, but how can I avoid it?
    }
}

ВЫВОД:

Foo is null? False
Foo.Bar is null? False
FooBase is null? False
FooBase.Bar is null? True

Проблема в том, что «FooBase.Bar» имеет значение NULL. Я понимаю, что модификатор new скрывает базовую реализацию, поэтому я знаю, что могу заставить ее работать, добавив (закомментированную) строку:

base.Bar = Bar; // I don't want to do this, but how can I avoid it?

Есть ли лучший способ? Чего мне не хватает?

Заранее спасибо!


РЕДАКТИРОВАТЬ (6/7/11)

Добавление более конкретного примера к моей проблеме. В основном, есть абстрактные реализации для Игры и Камеры (очень основные здесь ...). Абстрактная игра использует абстрактную камеру для основной работы. Производная игра использует свою производную камеру для своей специальной работы. В конце концов, мне нужно, чтобы как производные, так и абстрактные камеры работали в своей собственной области.

Я дал две разные реализации Camera и две реализации Game, чтобы (надеюсь) продемонстрировать гибкость, которую я ищу. Обратите внимание, что Game1 использует «взлом», чтобы заставить базовую камеру иметь значение - Game2 не делает этого. Game1 имеет поведение Я хочу, но не реализация Я хочу.

class GameAndCameraExample
{
    static void Main(string[] args)
    {
        new Game1().Play();
        Console.WriteLine();
        new Game2().Play();

        Console.ReadLine();
    }

    // abstract camera
    abstract class CameraBase
    {
        public void SpecialCameraBaseMethod()
        {
            Console.WriteLine("SpecialCameraBaseMethod");
        }
    }

    // camera implementation
    class ChaseCamera : CameraBase
    {
        public void SpecialChaseCameraMethod()
        {
            Console.WriteLine("SpecialChaseCameraMethod");
        }
    }

    // a different camera implementation
    class FlightCamera : CameraBase
    {
        public void SpecialFlightCameraMethod()
        {
            Console.WriteLine("SpecialFlightCameraMethod");
        }
    }

    // abstract game
    abstract class GameBase
    {
        public virtual CameraBase Camera { get; set; }

        public GameBase() { }

        public virtual void Play()
        {
            Console.WriteLine("GameBase.Camera is null? {0}", Camera == null);
            if(Camera != null) // it will be null for Game2 :-(
                Camera.SpecialCameraBaseMethod(); 
        }
    }

    // awesome game using chase cameras
    class Game1 : GameBase
    {
        public new ChaseCamera Camera { get; set; }

        public Game1() : base()
        {
            Camera = new ChaseCamera();
            base.Camera = Camera; // HACK: How can I avoid this?
        }

        public override void Play()
        {
            Console.WriteLine("Game.Camera is null? {0}", Camera == null);
            Camera.SpecialChaseCameraMethod();
            base.Play();
        }
    }

    // even more awesome game using flight cameras
    class Game2 : GameBase
    {
        public new FlightCamera Camera { get; set; }
        public Game2()
            : base()
        {
            Camera = new FlightCamera();
        }

        public override void Play()
        {
            Console.WriteLine("Game.Camera is null? {0}", Camera == null);
            Camera.SpecialFlightCameraMethod();
            base.Play();
        }
    }
}

ВЫВОД:

Game.Camera is null? False
SpecialChaseCameraMethod
GameBase.Camera is null? False
SpecialCameraBaseMethod

Game.Camera is null? False
SpecialFlightCameraMethod
GameBase.Camera is null? True

Является ли наследование даже лучшим подходом для этого?

Ответы [ 4 ]

4 голосов
/ 05 июня 2011

Вам нужно использовать override вместо new.

Вы все еще можете вернуть Bar объект, хотя он все равно будет возвращаться как BarBase.Но override - единственный способ убедиться, что свойство Foo используется вместо свойства FooBase, когда объект Foo приведен как FooBase.

Обобщения могут помочьв этой ситуации, в основном для класса FooBase:

class FooBase<T> where T: BarBase {
    public virtual T Bar { get; set; }
    public FooBase() { }
}

class Foo : FooBase<Bar> {
    public override Bar Bar { get; set; }
    public Foo() : base() {
        Bar = new Bar();
    }
}

А вот модификации Main():

static void Main( string[] args ) {
    var foo = new Foo();
    var foobase = foo as FooBase<Bar>;

    Console.WriteLine( "Foo is null? {0}", foo == null );
    Console.WriteLine( "Foo.Bar is null? {0}", foo.Bar == null );
    Console.WriteLine( "FooBase is null? {0}", foobase == null );
    Console.WriteLine( "FooBase.Bar is null? {0}", foobase.Bar == null );
    Console.ReadKey();
}

Ограничение универсального параметра T до BarBase означает, что любой подкласс FooBase может присоединиться к любому подклассу BarBase или просто иметь дело с BarBase:

class VagueFoo : FooBase<BarBase> { }
2 голосов
/ 08 июня 2011

Я собираюсь представить ответ на свой вопрос. На самом деле два ответа. Вероятно, есть много других ответов, но ни один не находит их здесь.

Решение № 1: Параллельные иерархии

Эта идея была высказана @Joel B Fant в предыдущем комментарии. Я посмотрел, как реализован ADO.NET (через Reflector), чтобы придумать реализацию. Я не в восторге от этого шаблона, потому что он означает несколько свойств для одного и того же семейства объектов, но это разумное решение.

(Предположим, что CameraBase, FlightCamera и ChaseCamera из моего вопроса все еще определены для экономии места)

КОД

// abstract game
abstract class GameBase
{
    public virtual CameraBase CameraBase { get; set; }
    public GameBase() { }
    public virtual void Play()
    {
        Console.WriteLine("GameBase.CameraBase is null? {0}", CameraBase == null);
        if (CameraBase != null)
            CameraBase.SpecialCameraBaseMethod();
    }
}

// awesome game using chase cameras
class Game1 : GameBase
{
    public override CameraBase CameraBase 
    { 
        get { return Camera; } 
        set { Camera = (ChaseCamera)value; } 
    }
    public ChaseCamera Camera { get; set; }
    public Game1()
    {
        Camera = new ChaseCamera();
    }
    public override void Play()
    {
        Console.WriteLine("Game.Camera is null? {0}", Camera == null);
        Camera.SpecialChaseCameraMethod();
        base.Play();
    }
}

Решение № 2: Наследование, инъекция и твист

У этого "шаблона" может быть настоящее имя, но я его не знаю. Это очень похоже на параллельные иерархии (как и мой оригинальный пример) за исключением того, что я внедряю камеру через конструктор и использую базовые свойства (с приведением) для доступа. Я полагаю, что мог бы избежать инъекции и сделать прямое присвоение базовому свойству (но почему бы избежать инъекции? Это так полезно!). твист - это просто необходимое литье для свойств. Это не чисто, но и не безобразно. Это мое текущее предпочтительное решение.

КОД

// must inject the camera for this solution
static void Main(string[] args)
{
    new Game1(new ChaseCamera()).Play();
    Console.ReadLine();
}

// abstract game
abstract class GameBase
{
    public virtual CameraBase Camera { get; set; }
    public GameBase(CameraBase camera) // injection
    {
        Camera = camera;
    }
    public virtual void Play()
    {
        Console.WriteLine("GameBase.Camera is null? {0}", Camera == null);
        if (Camera != null)
            Camera.SpecialCameraBaseMethod();
    }
}

// awesome game using chase cameras
class Game1 : GameBase
{
    public new ChaseCamera Camera 
    { 
        get { return (ChaseCamera)base.Camera; } 
        set { base.Camera = value; } 
    }
    public Game1(ChaseCamera camera) : base(camera) { } // injection
    public override void Play()
    {
        Console.WriteLine("Game.Camera is null? {0}", Camera == null);
        Camera.SpecialChaseCameraMethod();
        base.Play();
    }
}

Оба эти решения дают мне желаемые результаты с приемлемой реализацией.

0 голосов
/ 10 июня 2011

Вот подсказка для решения:

class GameAndCameraExample
{
    static void Main(string[] args)
    {
        new Game1().Play();
        Console.WriteLine();
        new Game2().Play();
        Console.ReadLine();
    }    

    // abstract camera  
    abstract class CameraBase
    {
        public void SpecialCameraBaseMethod()
        {
            Console.WriteLine("SpecialCameraBaseMethod");
        }
    }    

    // camera implementation    
    class ChaseCamera : CameraBase
    {
        public void SpecialChaseCameraMethod()
        {
            Console.WriteLine("SpecialChaseCameraMethod");
        }
    }    

    // a different camera implementation   
    class FlightCamera : CameraBase
    {
        public void SpecialFlightCameraMethod()
        {
            Console.WriteLine("SpecialFlightCameraMethod");
        }
    }    

    // abstract game   
    abstract class GameBase<T> where T : CameraBase, new()
    {
        public T Camera { get; set; }

        public GameBase()
        {
            Camera = new T();
        }

        public virtual void Play()
        {
            Console.WriteLine("GameBase.Camera is null? {0}", Camera == null); 
            if (Camera != null) // it will be null for Game2 :-(              
                Camera.SpecialCameraBaseMethod();
        }
    }   

    // awesome game using chase cameras  
    class Game1 : GameBase<ChaseCamera>
    {
        public override void Play()
        {
            Console.WriteLine("Game.Camera is null? {0}", Camera == null); 
            Camera.SpecialChaseCameraMethod(); 
            base.Play();
        }
    }

    // even more awesome game using flight cameras    
    class Game2 : GameBase<FlightCamera>
    {
        public override void Play()
        {
            Console.WriteLine("Game.Camera is null? {0}", Camera == null); 
            Camera.SpecialFlightCameraMethod(); 
            base.Play();
        }
    }
}

Надеюсь, вы найдете это полезным.-Zsolt

0 голосов
/ 07 июня 2011
class Foo : FooBase
{
    public override BarBase Bar { get; set; }
    public Foo() : base()
    {
        // populate our modified property with the derived class
        Bar = new Bar();
        //base.Bar = Bar; // I don't want to do this, but how can I avoid it?
    }
}

Вы можете попробовать это: изменить тип свойства на BarBase и использовать переопределение.Затем используйте полиморфизм, чтобы назначить объект Bar производного класса свойству BarBase.Панель свойств является виртуальной, поэтому правильный объект будет возвращен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...