How can I constrain a method that takes an Action<

2019-09-11 12:59发布

问题:

If I have a method signature like this;

public void Execute(Action<T> action) {
    ...
}

but I want to constrain it so that the supplied 'action' only comes from the class 'MyActions', how can this be achieved in c#?


In order to try and make it clearer;

For example I have a class called MyActions;

public class MyActions {
    public void FirstAction<T>(T item) {
        ...
    }

    public void SecondAction<T>(T item) {
        ...
    }
}

I have the above method and I want it so that the only actions that the method will accept are those from this class.

I do not want it to be possible for anyone to supply an arbitrary action, they must come from the class 'MyActions'.

Can it be done?

Regards,

Ryan.

回答1:

One way to make clear your intent that you want to accept only members of MyActions would be something like:

public void Execute(Func<MyActions, Action<T>> actionSelector)

You could then call it like

Execute(actions => actions.SomeAction);

Of course, this works only if the methods (or delegate properties) of MyActions are not static.

And like I said, this makes your intent clear, but doesn't actually constraint the methods, someone could still call your method like:

Execute(ignored => otherAction);

In general this seems to me like a weird thing to want.



回答2:

You could do this using Reflection however it would hurt performance and I don't recommend it as it is just plain a bad idea. I think you should try a less specific question; how about telling us what you want to achieve that has led you down this path and maybe we can help you accomplish that another way.



回答3:

public sealed class MyActions <T> where T:new()
{ 
    public enum Methods { First, Second }  

    static Action<T>[] MethodsArray = { Method1, Method2 };
    public virtual void Execute(T value, Methods methods = Methods.First)
    {
        MethodsArray[(int) methods].Invoke(value);
    }
    private void Method1(T value) {}
    private void Method2(T value) {}

}

//public class MiActionsSeconds<T> : MyActions <T>    where T:new()
// {
//     public override void Execute( T value, Methods methods = Methods.Second )
//     {
//         base.Execute( value, methods );
//     }
// }

public class Foo<T>  where T:new()
{
    public void Execute(MyActions<T> instance, MyActions<T>.Methods methods = Methods.First)
    {
        instance.Execute( default( T ) );
        instance.Execute( default( T ), MyActions<T>.Methods.Second );
    }

    public void Test()
    {
      //  Execute( new MiActionsSeconds<T>( ) );
    }
}


回答4:

A big problem with this is that once you get Action<...> foo = MyActions.FirstAction to pass into Execute you have retrieved a value, and its type is simply Action<...>. There is nothing in the type that shows it was previously stored in a reference in MyActions. You can check to see if MyActions contains the action being passed by comparing the references, but that's a runtime check.

A partial way to put this information in the type is to have a MyAction class that everything in MyActions derives from. Then you can restrict Execute to take MyAction instances. However, a user could just as easily as you create their own MyAction subclass.

This smells like unnecessary exposure of internals: if only that small set of Actions work, then why do you ask the user for an Action? An enum that allows the user to specify intent or just several different Execute methods makes more sense.



标签: c# delegates