What is the best way to deal with optional delegates in a c# constructor?

时间:2016-12-09 13:07:27

标签: c# delegates constructor-chaining

I'm refactoring a function that takes in an optional delegate through the constructor. The delegate is run when events are triggered within the class. If the delegate isn't passed then a local default function is used instead:

public class Foo
{
    int    _memberVariable;
    readonly Action _onEventOne;
    readonly Action _onEventTwo;

    public Foo(Action onEventOne, Action onEventTwo = null)
    {
        _memberVariable = 0;

        _onEventOne = onEventOne;
        _onEventTwo = onEventTwo ?? DefaultEventTwo;

        _onEventOne();
    }

    private void DefaultEventTwo()
    {
        ++_memberVariable;
    }
}

I'm looking to remove the default value (this is a public interface so overloading would be preferable), and this is in production code so I don't want to change the interface unless I must.

In an ideal world I'd use constructor chaining:

public Foo(Action onEventOne) : this(onEventOne, DefaultEventTwo)
{
    //CS0120 An object reference is required for the non-static field, method, or property 'Foo.DefaultEventTwo()
}

(I understand why this doesn't work, just giving an example of the kind of solution I would use if this weren't a constructor).

Because the delegates are readonly I can't set them in a shared initialize type function.

Is there a better way to handle a case like than just passing in null and then catching it in the main constructor? It doesn't feel very elegant, and I'd like to be able to catch a null Action as an exception ideally (for if an external caller used null instead of using the overloaded constructor). I could remove the readonly from the delegates but again it doesn't feel like a great solution as they are really readonly.

Any thoughts would be appreciated.

2 个答案:

答案 0 :(得分:1)

我能让这项工作成功的唯一方法就是让它变得丑陋(我个人认为)。

您必须提供静态方法,但该静态方法可以使用对此的引用来获取实际方法。

这就是我想出来的。

public Foo(Action onEventOne) : this(onEventOne, self => self.DefaultEventTwo)
{
    //CS0120 An object reference is required for the non-static field, method, or property 'Foo.DefaultEventTwo()
}

public Foo(Action onEventOne, Action onEventTwo = null) : this(onEventOne, self => onEventTwo)
{ }

// private constructor, just for the sake of getting it working
private Foo(Action onEventOne, Func<Foo, Action> onEventTwo = null)
{
    _memberVariable = 0;

    _onEventOne = onEventOne;
    _onEventTwo = onEventTwo(this); // <--

    _onEventOne();
}

self => self.DefaultEventTwo是获取操作的静态函数。该函数用于调用onEventTwo(this)以获取this实例的默认事件。

答案 1 :(得分:1)

我错过了什么吗?

public class Foo
{
    int    _memberVariable;
    readonly Action _onEventOne;
    readonly Action _onEventTwo;

    public Foo(Action onEventOne): this(onEventOne, null) { }
    public Foo(Action onEventOne, Action onEventTwo)
    {
        _memberVariable = 0;

        _onEventOne = onEventOne;
        _onEventTwo = onEventTwo ?? DefaultEventTwo;

        _onEventOne();
    }

    private void DefaultEventTwo()
    {
        ++_memberVariable;
    }
}

您所要做的就是删除default-value并创建一个只有一个参数的新构造函数。现在,在最详细的构造函数(原始构造函数)中,检查提供的值是否为null,如果是,则将_onEventTwo设置为DefaultEventTwo

为避免任何人使用简化的构造函数,只需将其设为internal即可。

编辑:关于异常处理。如何使用内部构造函数作为“主要” - 所有其他人使用参数调用来指示调用的来源:

internal Foo(Action onEventOne): this(onEventOne, null, true) { }
// public API: NULL not allwoed as param
public Foo(Action onEventOne, Action onEventTwo) : this(onEventOne, onEventTwo, false) { }
internal Foo(Action onEventOne, Action onEventTwo, bool internalUse)
{
    _memberVariable = 0;

    _onEventOne = onEventOne;
    if(onEventTwo == null)
    {
        if(!internalUse) throw new ArgumentNullException("onEventTwo");
        else this._onEventTwo = DefaultEventTwo;
    }
    _onEventOne();
}