обеспечение четко определенного состояния объекта, когда конструктор выдает исключение - PullRequest
2 голосов
/ 20 мая 2010

У меня есть приложение 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);
    }
}

Спасибо, PaulH


Редактировать: Удален неиспользуемый код

Контейнер внутри 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);
    }
}

Ответы [ 2 ]

0 голосов
/ 20 мая 2010

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

// 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();
        }
    }
}
0 голосов
/ 20 мая 2010

Почему container.Add(this) появляется перед InitializeComponent()? По определению вы добавляете неинициализированный компонент в контейнер; это может вызвать проблемы, даже если метод инициализации не не удастся.

Просто переключите заказ:

    InitializeComponent();
    container.Add(this);

Таким образом, если InitializeComponent сгенерирует, компонент никогда не добавит себя в контейнер. Нет необходимости в специальной очистке.

Я не уверен, зачем вам вообще нужен этот конструктор, но если по какой-то причине он должен быть в том порядке, в котором вы уже находитесь (т.е. метод InitializeComponent зависит от container ... eek) затем поместите обработку исключений в сам конструктор:

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

Но не делайте этого, если вам действительно не нужно; гораздо лучше просто не добавлять компонент в контейнер, пока он не будет полностью инициализирован и стабилен.

...