Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can an Action be assigned to a void delegate implicitly?

I have a class that needs constructed with a void delegate:

//an object that's constructed with a "void delegate of no params"
public class BindableCommand
{
    public delegate void ExecuteMethod();
    private readonly ExecuteMethod _executeMethod;

    public BindableCommand(ExecuteMethod executeMethod)
    {
        _executeMethod = executeMethod;
    }
}

It works when its constructed in the following way:

public class Test
{
    public static void Main()
    {
        //creates a bindable command
        BindableCommand b = Create();
    }

    private static BindableCommand Create(){
        BindableCommand b = new BindableCommand(Function);
        return b;
    }

    private static void Function(){}
}

I would now like to pass Function as an argument before constructing BindableCommand.
My attempt fails to compile:

public class Test
{
    public static void Main()
    {
        //creates a bindable command
        BindableCommand b = Create(Function);
    }

    private static BindableCommand Create(Action action){
        BindableCommand b = new BindableCommand(action);
        return b;
    }

    private static void Function(){}
}

prog.cs(20,19): warning CS0219: The variable `b' is assigned but its value is never used
prog.cs(24,23): error CS1502: The best overloaded method match for `BindableCommand.BindableCommand(BindableCommand.ExecuteMethod)' has some invalid arguments
prog.cs(9,12): (Location of the symbol related to previous error)
prog.cs(24,43): error CS1503: Argument `#1' cannot convert `System.Action' expression to type `BindableCommand.ExecuteMethod'

But I thought an Action was a void delegate()?

I can't pass a void delegate:

private static BindableCommand Create(delegate void action){/* ... */}

It seems I have to do the following:

private static BindableCommand Create(BindableCommand.ExecuteMethod action){/* ... */}

Is there a way to have the cast occur automatically?

like image 526
Trevor Hickey Avatar asked Dec 02 '22 14:12

Trevor Hickey


1 Answers

The feature you want C# to have is called "structural delegate conversions". That is, if you have two delegate types and they both take an int and return a string, then you should be able to assign values of one type to another.

C# does not have this feature. Many people, myself included, regret that structural typing was not introduced on delegates in .NET 1.0. Why then was it not included?

The design consideration was that you might want to have semantic information encoded in your delegate types:

delegate R Pure<A, R>(A a);
delegate R Impure<A, R>(A a);

A "pure" function is a function with no side effects, where the outputs are uniquely determined by the inputs. You might want to say, for instance, that a comparison function must be pure. We don't expect a comparison to change its value if the inputs don't change, and we don't expect it to produce a side effect.

Plainly it would be wrong to be able to assign an impure delegate value to a variable of pure delegate type. So the type system prevents it, even if the delegates are structurally identical.

In practice, few people put semantic information like this in delegates. It would have been more convenient to allow structural conversions.

There is one way to do a conversion between structural delegate types but it is not very nice:

Action a = whatever;
ExecuteMethod e = a.Invoke;

That is, the delegate created for e is a delegate to the invoke method of delegate a. So invoking e invokes a, which invokes the desired method. This is an extra step of indirection, but the performance penalty is not too great, one hopes.

like image 129
Eric Lippert Avatar answered Dec 22 '22 21:12

Eric Lippert