【问题标题】:"new" modifier causes base implementations to have NULL property values“new”修饰符导致基本实现具有 NULL 属性值
【发布时间】:2011-09-08 13:08:57
【问题描述】:

我正在尝试使用多态性让派生类使用派生属性而不是基属性进行操作。我不确定如何用更清晰的语言表达,所以这里有一个输出示例:

// 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 为空?错误的 Foo.Bar 为空?错误的 FooBase 为空?错误的 FooBase.Bar 为空?真的

问题是“FooBase.Bar”为 NULL。我知道 new 修饰符隐藏了基本实现,这就是为什么我知道我可以通过添加(注释掉的)行使其工作:

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

有没有更好的方法?我错过了什么?

提前致谢!


编辑(2011 年 6 月 7 日)

为我的问题添加更具体的示例。基本上,有游戏和相机的抽象实现(这里非常基本......)。抽象游戏使用抽象相机进行基本工作。派生游戏使用其派生相机进行专业工作。最后,我需要派生和抽象相机在它们自己的范围内工作。

我已经给出了两个不同的 Camera 实现和两个 Game 实现来(希望)展示我正在寻找的灵活性。请注意,Game1 使用“hack”来强制基本相机具有值 - 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 为空?错误的 特殊追逐相机法 GameBase.Camera 为空?错误的 特殊CameraBaseMethod Game.Camera 为空?错误的 特殊飞行相机法 GameBase.Camera 为空?真的

继承是否是最好的方法?

【问题讨论】:

  • 添加了一个新示例以更好地反映我的实际问题

标签: c# .net polymorphism derived-class


【解决方案1】:

您需要使用override 而不是new

您仍然可以返回 Bar 对象,以为它仍会返回为 BarBase。但是当Foo 对象被转换为FooBase 时,override 是确保使用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();
}

泛型参数TBarBase的约束意味着FooBase的任何子类都可以选择加入BarBase的任何子类,或者只处理BarBase

class VagueFoo : FooBase<BarBase> { }

【讨论】:

  • 谢谢,但覆盖要求成员匹配。 "'Foo.Bar': type 必须是 'BarBase' 才能匹配被覆盖的成员 'FooBase.Bar'"
  • 对,我看看能不能重新设计一下,看看泛型参数能不能帮到你。
  • 虽然这个解决方案有效(谢谢!),一旦你有多个需要相同行为的属性,它就会变得很丑 - 特别是如果 foo 是一个相当常见的类型用过的。可能是我需要考虑对我的架构采用不同的方法。我会稍微开放一下,看看是否提供了其他解决方案。
  • 我想这取决于你的类层次结构。 foo/bar 示例与您的真实类层次结构有多简化和远离?它更像是基于 ADO.NET 类的派生类吗? (OdbcCommandSqlClientCommand 基于DbCommand 等:并行层次结构)
  • 这个例子被简化来演示我的问题。我实际的类层次结构可能有许多类似的场景。我将考虑其他技术(如组合)而不是继承。您的 ADO.NET 示例可能是我想要完成的一个很好的比较。
【解决方案2】:

我将回答我自己的问题。其实,两个答案。可能还有许多其他答案,但没有一个在这里找到自己的方式。

解决方案 #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:继承、注入和扭曲

这个“模式”可能有一个真实的名字,但我不知道。它与 Parallel Hierarchies(以及我的原始示例)非常相似,只是我通过构造函数注入相机并使用基本属性(带有强制转换)进行访问。我想我本可以避免注入并直接分配给基本属性(但为什么要避免注入?它太有用了!)。 twist 只是属性所需的转换。它不干净,但也不丑。这是我目前首选的解决方案。

代码

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

这两种解决方案都以可接受的实施方式为我提供了预期的结果。

【讨论】:

    【解决方案3】:
    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 属性。属性栏是虚拟的,因此将返回正确的对象。

    【讨论】:

    • 正确。不幸的是,这意味着 Foo 的任何消费者都需要转换 Bar 的结果才能访问 Bar 而不是 Bar 中定义的方法/属性i>酒吧基地。这正是我希望避免的。
    【解决方案4】:

    以下是解决方法的提示:

    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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-15
      • 2017-11-30
      相关资源
      最近更新 更多