Domanda

Quick question.

In the second example on this documentation page (the second code block, featuring a method called CompareDinosByLength), the Sort method is called as such:

dinosaurs.Sort(CompareDinosByLength);

Why is it that the Sort method didn't need an explicitly declared delegate, as I would have thought by reading the Delegate documentation? Before I found that example, I was attempting to do it like so:

delegate int CompareDinosDel(string first, string second);
CompareDinosDel newDel = CompareDinosByLength;
dinosaurs.Sort(newDel);

But I kept getting errors related to the delegate / delegate method not being proper Comparers.

Shouldn't both work?

È stato utile?

Soluzione 2

dinosaurs.Sort(CompareDinosByLength);

CompareDinosDel newDel = CompareDinosByLength;
dinosaurs.Sort(newDel);

Shouldn't both work?

No, because you are passing two very different things into those two function calls.

The key here is to recognize that, in both cases, what you actually pass into the method is a delegate. In the first case, the compiler is implicitly creating a delegate of the correct type for you, even though you didn't explicitly ask it to. In the second case, you're making your own delegate, but it's the wrong type, so that attempt will fail.

Starting with .NET 2.0, the C# compiler allow you to skip explicitly create delegates in many situations. If you use a method name in a context where a delegate is expected, and the compiler can verify that the method signature and delegate signature match, it will implicitly construct a delegate instance using the method. That is, instead of doing this (the "old" way)

this.SubmitButton.Click += new System.EventHandler(this.SubmitButton_Click);

You can now do this:

this.SubmitButton.Click += this.SubmitButton_Click;

Visual Studio itself will still generate the older syntax, I assume because it still works and because it's not worth the developer's time to go messing around with it for very little benefit. However, most popular code analysis tools will flag the redundant delegate creation if you use it in your own code.

This same technique works anywhere you have a method (technically a "method group", since one method name can refer to more than one overload), and you assign it to a variable of a delegate type. Passing a method as a parameter into another method is the same type of assignment operation: you are "assigning" the actual parameter at the call site to the formal parameter in the method body, so the compiler does the same thing. In other words, the following two method calls do exactly the same thing:

dinosaurs.Sort(CompareDinosByLength);
dinosaurs.Sort(new Comparison<string>(CompareDinosByLength));

Your unsuccessful attempt to make a delegate, on the other hand, did something slightly different:

dinosaurs.Sort(new CompareDinosDel(CompareDinosByLength));

This time, you told the compiler exactly what kind of delegate you wanted, but that's not the kind of delegate that the method expected. In general, the compiler isn't going to try to second guess what you told it do to; if you ask it to do something that looks "fishy", it will produce an error (in this case, a type mismatch error).

This behavior is similar to what would happen if you tried to do this:

public class A
{ 
   public int x;
}

public class B
{ 
   public int x;
}

public void Foo(A a) { }

public void Bar()
{
    B b = new B();
    this.Foo(b);
}

In this case, A and B are two distinct types, even though their "type signature" is exactly the same. Any line of code that works on an A will also work equally well on a B, but yet, we cannot use them interchangeably. Delegates are types like any other types, and C#'s type safety rules require that we use the correct delegate types where we need them, and can't get away with just using a close enough type.

The reason this is a good thing is because a delegate type may have a lot more meaning that just it's technical components would imply. Like any other data type, when we create delegates for our applications, we usually apply some kind of semantic meaning to those types. We expect, for example, that if we have a ThreadStart delegate, that it's going to be associated with a method that runs when a new thread starts. the delegate's signature is about as simple as you get (no parameters, no return value) but the implication behind the delegate is very important.

Because of that, we generally want the compiler to tell us if we try to use the wrong delegate type in the wrong place. More often than not, that's probably a sign that we are about to do something that may compile, and even run, but is likely to do the wrong thing. That's never something you want from your program.


While all that is true, it's also true that often times you really don't want to assign any semantic meaning to your delegates, or else, the meaning is assigned by some other part of your application. Sometimes you really do just want to pass around an arbitrary piece of code that has to run later. This is very common with functional-style programs or asynchronous programs, where you get things like continuations, callbacks, or user-supplied predicates (look at the various LINQ methods, for example). .NET 3.5 and onward supply a very useful set of completely generic delegates, in the Action and Func family, for this purpose.

Altri suggerimenti

Why is it that the Sort method didn't need an explicitly declared delegate?

C# permits a method group -- that is, a method which is named without having the (...) argument list to invoke it -- to be used in a context where a delegate is expected. The compiler performs overload resolution on the method group as though the method group had been invoked with arguments of the types of the delegate's formal parameters. This determines which method of the method group should be used to create the delegate.

This overload resolution process can sometimes lead to unusual situations involving method type inference when the method group is undergoing overload resolution to a delegate type which is a formal parameter type of a generic method; Sort, fortunately is not a generic method, so these oddities do not come into play.

This feature was added to C# 2.0; before that a method group had to be converted to a delegate via

new MyDelegate(MyMethod)

I keep getting errors related to the delegate / delegate method not being proper Comparers. Shouldn't both work?

Unfortunately, no. C# does not have structural identity on delegate types. That is:

delegate void Foo();
delegate void Bar();
...
Foo foo = ()=>{};
Bar bar = foo; // ERROR!

Even though Foo and Bar are structurally identical, the compiler disallows the conversion. You can however use the previous trick:

Bar bar = foo.Invoke;

This is equivalent to

Bar bar = new Bar(foo.Invoke);

However the new bar has as its action to invoke foo; it goes through a level of indirection.

This feature does make some sense.

Reason one:

You don't expect structural identity to work in other places:

struct Point { int x; int y; ... }
struct Pair { int key; int value; ... }
....
Point point = whatever;
Pair pair = point; // ERROR

Reason two:

You might want to say:

delegate int PureMethod(int); 

And have a convention that PureMethod delegates are "pure" -- that is, the methods they represent do not throw, always return, return a value computed only from their argument, and produce no side effects. It should be an error to say

Func<int, int> f = x => { Console.WriteLine(x); return x+1; };
PureMethod p = f;

Because f is not pure.

However in hindsight people do not actually make semantics-laden delegates. It is a pain point that a value of type Predicate<int> cannot be assigned to a variable of type Func<int, bool> and vice versa.

If we had to do it all over again, I suspect that delegates would have structural identity in the CLR.

Finally, I note that VB is much more forgiving about inter-assigning mixed delegate types; it automatically builds an adapter delegate if it needs to. This can be confusing because sometimes it looks like referential identity is maintained when in fact it is not, but this is in keeping with the VB philosophy of "just make my code work".

Consider the following code:

public class Foo
{
    public int Bar { get; set; }
}

public class SomeOtherFoo
{
    public int Bar { get; set; }
}

Should I be able to say:

Foo foo = new SomeOtherFoo();

That won't work in C# either. When you have two different types that have the same body/implementation, they are still different types. Two classes with the same properties are still different classes. Two different delegates with the same signature are still different delegates.

The Sort method has already defined the delegate type, and you need to match it. This is very much like it defining a class that it needs to accept as a parameter; you can't just pass in another type with the same properties and methods.

This is what it means for a language to be statically typed. An alternate type system would be to use "Duck Typing" in which the language doesn't apply the constraint that a variable be of a specific type, but rather that it has a specific set of members. In other words, "If it walks like a duck, and quacks like a duck, pretend it's a duck." That is opposed to the style of typing that says, "It must be a duck, period, even if it knows how to walk and quack."

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top