【问题标题】:Execute a derived constructor before the base constructor in C#在 C# 中的基本构造函数之前执行派生构造函数
【发布时间】:2010-10-18 14:53:32
【问题描述】:

我的问题是我想将一个对象传递给派生类,但必须在基类构造函数之前完成,因为基类会立即调用派生类使用该对象的Start()方法。

这是基类的摘录,(为方便起见,从BarcodeScanner 重命名)。

public abstract class MyBase
{    
    public MyBase()
    {
        if (Initialize())
            this.Start();
    }

    public abstract bool Initialize();
    public abstract void Start();
}

这是我正在创建的派生类。

class MyDerived : MyBase
{
    private string sampleObject;

    public MyDerived (string initObject)
    {
        sampleObject = initObject;
    }

    public override bool Initialize() 
    { 
        return GetDevice();
    }
    public override void Start() 
    { 
        Console.WriteLine("Processing " + sampleObject.ToString()); 
    }
}

我怀疑你可以让 C# 在基构造函数之前执行派生构造函数;所以我真的只是在寻找一种解决方案,在使用对象之前将对象传递给派生类。

我通过将 Initialize/Start if 块放入 MyDerived 构造函数中解决了这个问题。但是,还有其他类派生自基类;所以我最终不得不在每个派生类中重复这个初始化/启动代码块。我希望看到修改基类的替代方法。

【问题讨论】:

    标签: c# constructor abstract-class derived-class


    【解决方案1】:

    您尝试做的事情在 C# 中是不可能的。基类中的构造函数必须在任何派生类的构造函数之前运行,否则可能会损坏对象状态。子对象必须能够假定其基础已完全构建且可用。

    【讨论】:

    • 好吧,可以创建空的重写 Initialize/Start 方法,并将这些代码移动到 derivedInitialize/derivedStart 方法中,然后在派生构造函数中调用 if (derivedInitialize()) derivedStart()。可能没有优雅的解决方案,但还有其他方法。
    • 虽然框架尝试防止暴露未完全构造的对象通常是件好事,但有时让类访问此类对象会更有用,但需要注意的是它必须准备好处理部分构造的对象。例如,如果一个类将封装一个IDisposable 对象,其生命周期将匹配它自己的,那么使用字段初始化器来设置它会很方便;唉,在 C# 中没有干净的方法可以安全地做到这一点。如果基类构造函数抛出,任何东西都无法获得需要处理的东西的引用。
    【解决方案2】:

    恕我直言,您的设计是错误的。您不应该从构造函数中启动该过程。您的消费代码应在需要时显式调用 Start() 方法。

    【讨论】:

    • +1。这家伙是对的,你需要.Start() / .Initialise()。不要在构造函数中调用虚方法。
    • +1。 C++ 限制来自构造函数的虚拟调用是有原因的。
    • 我完全同意 MSDN 的设计并不完美,但它已经实现并且与不需要传入对象的派生类一起工作。
    • MSDN 提倡这样的设计?可以发个链接吗?
    • +1。这就是著名的“InitializeComponent”出现的原因。您应该吐出构造和初始化逻辑。让你的“初始化”让我们说“受保护的虚拟”并在派生类中覆盖它。您将能够决定“base.InitializeComponent”调用应该先还是最后。
    【解决方案3】:

    我会重新设计您的设计,以便在构造后调用 Initialize(可能还有 Start() - 尽管我通常将此作为一个由用户调用的公共方法)。

    如果您正在制作 BarcodeScanner,您可以在第一次扫描时执行此操作。只需使用派生类中的数据对您的成员进行惰性初始化即可。

    这将解决您的问题,而不会真正改变用户的使用情况。

    【讨论】:

    • 扫描是由设备异步发起的,所以不能基于扫描开始进行初始化。事后看来,微软应该将他们的 Start() 方法重命名为 Ready() 方法。由于 Start() 方法只准备扫描仪,但实际上并没有开始扫描。
    【解决方案4】:

    很抱歉添加到旧线程,但也许有人对另一个答案感兴趣。我找到了一种(IMO)巧妙的方法来处理逻辑,而不仅仅是(以及之后)在涉及继承的类构造函数中分配字段here。 如果您只想为这个特定的层次结构设置 if 而不是使用带有接口和扩展方法的通用解决方案,您可以在一个类树中使用相同的概念,如下所示:

    public abstract class MyBase
    {    
        protected MyBase()
        {
            Initialize(this) // just to illustrate this will never do anything as MyBase can never be the run time type.
        }
    
        protected bool IsInitialized { get; private set; } = false;
    
        protected static bool Initialize<T>(T instance) where T: MyBase
        {
            if (instance?.GetType() == typeof(T)) // check if this is called from the constructor of instance run time type
                return instance.IsInitialized || ( instance.IsInitialized = instance.Initialize() );
            return false;
        }
    
        protected abstract bool Initialize();
        public abstract void Start();
    }
    

    和派生:

    class MyDerived : MyBase
    {
        private string sampleObject;
        protected bool started = false;
    
        public MyDerived (string initObject)
        {
            sampleObject = initObject;
            if (Initialize(this)) // if this is the most derived constructor, this will run Initialize() and return whether it was successful
                this.Start();// EDIT: Just to illustrate. Normally constructors should only initialize an instance and not perform operations on it (as mentioned in other answers).
        }
    
        protected override bool Initialize() 
        { 
           return GetDevice();
        }
    
        public override void Start() 
        { 
            // if Start() would be protected, we don't need the IsInitialized property and we can move this check to the constructor on the returned value of the Initialize<T>() call.
            if (!IsInitialized) throw new InvalidOperationException("Initialization failed.");
            // if you want to have this method exposed public, we need to check if this instance is successfully initialized from the constructor and not in started state already.
            if (started) return;
    
            Console.WriteLine("Processing " + sampleObject.ToString()); 
            started = true;
            if (!Run(sampleObject)) started = false;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-17
      • 2018-07-21
      • 2015-08-18
      • 2016-07-19
      • 1970-01-01
      • 2022-12-03
      • 2020-11-28
      相关资源
      最近更新 更多