【问题标题】:ensuring a well-defined object state when a constructor throws an exception当构造函数抛出异常时确保明确定义的对象状态
【发布时间】:2010-05-20 17:13:44
【问题描述】:

我有一个 Visual Studio 2008 C# .NET 2.0CF 应用程序。我正在使用一个组件库,从中派生出两个具体组件。应用程序首先尝试使用SomeDisposableComponent。它的构造函数抛出异常,因为它需要一个不可用的特性。然后,应用程序尝试SomeOtherDisposableComponent。它的构建成功了。

问题是第一个组件的构造函数在抛出异常之前已经将自己添加到表单的组件容器中。因此,当表单被处理时,第一个组件的Dispose() 成员被调用,即使该对象从未完全构造。这会导致第二个组件的析构函数出现问题。

如何确保当第一个组件在构造时抛出异常时,对它的引用被删除?

public abstract class SomeDisposableComponentBase : Component
{
    private System.ComponentModel.IContainer components;

    private SomeInternalDisposable s_ = new SomeInternalDisposable();

    protected SomeDisposableComponentBase()
    {
        Initializecomponent();
    }

    protected SomeDisposableComponentBase(IContainer container)
    {
        container.Add(this);
        Initializecomponent();
    }

    private void InitializeComponent()
    {
        components = new System.ComponentModel.Container();
    }

    protected abstract void Foo();

    #region IDisposable Members
    bool disposed_;

    protected override void Dispose(bool disposing)
    {
        // called twice. the first time for the component that failed to initialize properly.
        // the second for the one that was used.
        if (!disposed_)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }

            // on the second call, this throws an exception because it is already disposed.
            s_.Close();
            disposed_ = true;
        }
        base.Dispose(disposing);
    }
    #endregion    
}

public SomeDisposableComponent : SomeDisposableComponentBase
{
    public SomeDisposableComponent() : base()
    {
    }

    public SomeDisposableComponent(IContainer container) : base(container)
    {
        // This will throw an exception if it requires a feature that isn't available.
        SomeInitFunction();
    }

    protected override void Foo()
    {
        // Do something...
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
    }
}

public partial class my_form : Form
{
    private SomeDisposableComponentBase d_;

    public my_form()
    {
        InitializeComponent();
        if (null == components)
            components = new System.ComponentModel.Container();

        try
        {
            // try the default component
            d_ = new SomeDisposableComponent(components);
        }
        catch (System.Exception)
        {
            try
            {
                // the default component requires some feature that isn't available. Try a
                // backup component.
                d_ = new SomeOtherDisposableComponent(components);
            }
            catch (System.Exception e)
            {
                // display error to the user if no suitable component can be found.
            }
        }
    }

    /// exit button clicked
    private void Exit_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    /// from the my_form.designer.cs
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            // this function is executed as expected when the form is closed
            components.Dispose();
        }
        base.Dispose(disposing);
    }
}

谢谢, 保罗H


编辑:删除未使用的代码

SomeDisposableComponentBase 中的容器令人困惑。它与问题无关,我应该早点删除它。

public abstract class SomeDisposableComponentBase : Component
{       
    private SomeInternalDisposable s_ = new SomeInternalDisposable();

    protected SomeDisposableComponentBase()
    {
    }

    protected SomeDisposableComponentBase(IContainer container)
    {
        container.Add(this);
    }

    protected abstract void Foo();

    #region IDisposable Members
    bool disposed_;

    protected override void Dispose(bool disposing)
    {
        // called twice. the first time for the component that failed to initialize properly.
        // the second for the one that was used.
        if (!disposed_)
        {
            if (disposing)
            {
                // on the second call, this throws an exception because it is already disposed.
                s_.Close();
            }
            disposed_ = true;
        }
        base.Dispose(disposing);
    }
    #endregion    
}

public SomeDisposableComponent : SomeDisposableComponentBase
{
    public SomeDisposableComponent() : base()
    {
    }

    public SomeDisposableComponent(IContainer container) : base(container)
    {
        // This will throw an exception if it requires a feature that isn't available.
        SomeInitFunction();
    }

    protected override void Foo()
    {
        // Do something...
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
    }
}

public partial class my_form : Form
{
    private SomeDisposableComponentBase d_;

    public my_form()
    {
        InitializeComponent();
        if (null == components)
            components = new System.ComponentModel.Container();

        try
        {
            // try the default component
            d_ = new SomeDisposableComponent(components);
        }
        catch (System.Exception)
        {
            try
            {
                // the default component requires some feature that isn't available. Try a
                // backup component.
                d_ = new SomeOtherDisposableComponent(components);
            }
            catch (System.Exception e)
            {
                // display error to the user if no suitable component can be found.
            }
        }
    }

    /// exit button clicked
    private void Exit_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    /// from the my_form.designer.cs
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            // this function is executed as expected when the form is closed
            components.Dispose();
        }
        base.Dispose(disposing);
    }
}

【问题讨论】:

    标签: c# exception constructor c#-2.0 dispose


    【解决方案1】:

    您的表单应该负责将项目添加到其组件集合中 - 而不是您要添加到集合中的组件。在添加的组件中执行此操作会使您很难推断表单中发生的事情。

    // It is not obvious that the component is adding itself to the list
    new SomeDisposableComponent(components);
    
    // Instead, be explicit where it matters
    Component someDisposableComponent = new SomeDisposableComponent(components);
    this.components.Add(someDisposableComponent);
    

    接下来,要解决您的构造函数问题,您应该将逻辑移出构造函数。这通常是一种很好的做法——人们不希望构造函数有副作用,并且构造函数中的逻辑使类难以测试。如果您想保证每次创建实例时都会发生某些事情,请创建一个工厂并使其成为获取类实例的唯一方法(使类的构造函数内部化,或使用其他技术):

    public class SomeDisposableComponentFactory {
        public SomeDisposableComponent CreateInstance() {
            SomeDisposableComponent component = new SomeDisposableComponent();
            component.SomeInitFunction();
        }
    }
    

    最后,您应该考虑将用于选择和创建组件的逻辑从您的表单中移出并转移到通用组件工厂中:

    public class ComponentFactory {
    
        // The input parameter can be whatever you need to choose the right component
        public Component CreateInstance(object input) {
    
            if (input == something) {
                SomeDisposableComponent component = new SomeDisposableComponent();
                component.SomeInitFunction();
                return component;
            }
            else {
                return new AnotherComponent();
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      为什么container.Add(this) 出现在InitializeComponent() 之前?根据定义,您正在向容器中添加一个未初始化的组件;即使初始化方法没有失败,这也可能导致问题。

      只需切换顺序:

          InitializeComponent();
          container.Add(this);
      

      这样,如果InitializeComponent 抛出,组件将永远不会将自己添加到容器中。无需特别清理。

      我不确定你为什么需要那个构造函数,但如果由于某种原因它必须按照你已经拥有的顺序(即InitializeComponent 方法取决于container...eek) ,然后把异常处理放在构造函数本身:

      try
      {
          container.Add(this);
          InitializeComponent();
      }
      catch (WhateverException)
      {
          container.Remove(this);
      }
      

      但除非你真的必须这样做,否则不要这样做;在完全初始化和稳定之前不要将组件添加到容器中会更好。

      【讨论】:

      • 那个容器实际上总是空的并且未被使用。我已经消除了示例代码中的干扰。问题依然存在。问题的容器是 Form() 中的容器,而不是 SomeDisposableComponentBase
      猜你喜欢
      • 1970-01-01
      • 2012-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 2017-12-04
      相关资源
      最近更新 更多