Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Support delegate signature equivalence #4331

Open
HaloFour opened this issue Jun 24, 2015 · 6 comments
Open

Proposal: Support delegate signature equivalence #4331

HaloFour opened this issue Jun 24, 2015 · 6 comments

Comments

@HaloFour
Copy link

See Roslyn: Convert delegate types with compatible signature automatically

Currently the various CLR compilers do not permit treating one delegate of a specific signature as the same as another delegate of an identical signature. The compilers that do (VB.NET) do so by wrapping the existing delegate instance with the new delegate which incurs a performance penalty at the time of invocation.

It appears that for whatever reason, accidentally or otherwise, the CLR/JIT have no real issue using one delegate in place of another. The verifier does complain that an unexpected type is on the stack for the specified call but otherwise the program does execute.

I propose that the verifier be modified that if the type expected and the type on the stack are different but are both delegate types, and that the Invoke method on both delegates share the same signature that the verifier allow the operation.

This would permit delegates of identical signatures to be considered equivalent without the performance penalty either at the time of conversion or the time of invocation.

@masonwheeler
Copy link
Contributor

I think this is definitely worth doing. The purpose of verification is to prove that type safety will not be violated. If you can prove that all types involved in the delegate are equivalent, then type safety will not be violated, so there's no reason to report it as problematic.

@HaloFour
Copy link
Author

To note, this is not without type-safety issues. While the CLR will happily invoke the delegate it is not recognized as that type. For example:

public static boolean Test(Predicate<string> predicate) {
    object obj = predicate;
    boolean isPredicate = (obj is Predicate<string>);  // false!
    predicate = (Predicate<string>) obj;  // InvalidCastException!

    return predicate("Foo!");
}

Func<string, bool> predicate = () => true;
Test(predicate); // assuming compiler allowed this and running full-trust

Understandably that might make this problematic, but I'd love to at least use this proposal as a starting point to perhaps discuss delegate signature equivalence either through the CLR directly or perhaps with efficient conversion methods in the BCL.

@masonwheeler
Copy link
Contributor

@HaloFour Interesting point, but I have to wonder how relevant it is. I don't believe I've ever seen code that takes a variable of formal type X and applies an is operator, an as cast, or a hard cast on that variable using type X, and I can't imagine a scenario in which I would want to do so.

@iskiselev
Copy link

There is one more small problem - current implementation of Delegate.Combine will throw an exception if delegates of different types passed to it, even if their types are compatible.

@masonwheeler
Copy link
Contributor

@iskiselev This appears to be based on a runtime-level (C++) method called IsEquivalentTo, and there's a mechanism in place for determining equivalence of non-identical types.

That actually brings up an interesting idea: it implies that this whole thing could be implemented fairly simply by defining IsEquivalentTo for delegate types. Put all the logic there, in one centralized location, and then let compilers and PEVerify call IsEquivalentTo.

...maybe. Probably not the compiler, because the Type isn't created yet at the point where it needs to verify this. Not sure how PEVerify works under the hood, but hopefully it would be able to leverage this.

Good catch! :)

@dmitriyse
Copy link

dmitriyse commented Apr 8, 2020

Do we have some progress on this feature?
In BCL itself and in a huge amount of other libs we can find a code like this:

    if (obj is Action){ ... }
    else if (obj is Task) { .... }

if inside the obj is any non Action delegate like delegate void MyAction(), this code will be broken.

Approaches:

  • make is, as and cast operator behave especially for delegates with equivalent signature (still problem with obj.Type.Name == "Action" code)
  • Special Annotation and a compiler suport
// The same annotation technique as for "Nullable" or "Dynamic" for generics support.
public class SupportEquivalenceAttribute: Attribute
{
}

// C# syntax
public void MyNewMethod(var Action a)
{
   ....
}

// IL representation:
public void MyNewMethod([SupportEquivalence]Action a)
{
        if (a.Type.Name == "Action") // Compiler warning or error, because "a" could contain a different type of delegate
        {
        }
        if (Delegate.IsEquivalent<Action>(a)) // Correct implementation
        {
        }       
}

public void MyOldMethod(Action a)
{
       
}

delegate void MyAction();
public void MyCode()
{
    MyAction action = ()=>Console.WriteLine("HelloWorld");
    MyOldMethod(action); // Compiler Error!
    MyNewMethod(action) // Works, as implementation of MyNewMethod declares that it's support "Delegate equivalence feature".
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants