2

This must be a duplicate but i haven't found it. I've found this question which is related since it answers why it's recommended to use a method group instead of a lambda.

But how do i use an existing method group instead of a lambda if the method is not in the current class and the method is not static?

Say i have a list of ints which i want to convert to strings, i can use List.ConvertAll, but i need to pass a Converter<int, string> to it:

List<int> ints = new List<int> { 1 };
List<string> strings = ints.ConvertAll<string>(i => i.ToString());

This works, but it creates an unnecessary anonymous method with the lambda. So if Int32.ToString would be static and would take an int i could write:

List<string> strings = ints.ConvertAll<string>(Int32.ToString);

But that doesn't compile - of course. So how can i use a method group anyway?

If i'd create an instance method like this

string FooInt(int foo)
{
    return foo.ToString();
}

i could use strings = ints.ConvertAll<string>(FooInt);, but that is not what i want. I don't want to create a new method just to be able to use an existing.

Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • "This works, but it creates an unnecessary anonymous method with the lambda." - So? what is the problem? It's even better, because the code of that lambda tells you exactly what is does - which enhance readability and therefore maintainability. – Shlomi Borovitz May 28 '14 at 10:18
  • @ShlomiBorovitz: that's also my opinion, but ReSharper suggests to use the method instead of the redundant lambda. So i've asked this question even if it might be premature optimization. I'm asking just out of curiosity. Also, `Int32.ToString` is not that difficult to understand (if it would work). – Tim Schmelter May 28 '14 at 10:20
  • @TimSchmelter Sounds like resharper got it wrong. You can replace the code with `ints.Select(i => i.ToString()).ToList()` which would do exactly the same. Wondering if resharper would except that. – Shlomi Borovitz May 28 '14 at 10:23
  • @ShlomiBorovitz: that is exactly what i don't want. The LINQ query uses an anonymous method instead of `Int32.ToString`. Resharper would not like it (but i don't have it anyway). – Tim Schmelter May 28 '14 at 10:49
  • @TimSchmelter - I know it does, and sorry for not being clear on what I was intended: Because that (complaining about lambdas) is a strange behavior on the part of resharper, I was wondering if that would change things to resharper. Now, because lambdas are idiomatic to LINQ, it would even make some sense. – Shlomi Borovitz May 28 '14 at 11:17
  • 1
    @ShlomiBorovitz: maybe resharper doesn't even complain in this case, since i don't own it i cannot check it. But someone [recommended](http://stackoverflow.com/a/23906611/284240) it because resharper told him and i've heard it often before. I answered him that it's not possible because `ToString` isn't static, but i wasn't sure if that's true. – Tim Schmelter May 28 '14 at 11:21
  • @TimSchmelter Now I understand. And I even think we both agree on that readability is most important, while micro-optimizations are worthless. :) – Shlomi Borovitz May 28 '14 at 11:24
  • 1
    @ShlomiBorovitz: it's not only readability, you can also change a lambda easily, for example reverse a condition: `chars.Count(c => !Char.IsDigit(c))` whereas this method cannot be changed. – Tim Schmelter May 28 '14 at 11:55
  • In Hebrew we have a saying that is something like this "You're convincing the convinced" :) – Shlomi Borovitz May 28 '14 at 12:10

2 Answers2

7

There is an static method in the framework, that can be used to convert any integrated data type into a string, namely Convert.ToString:

List<int> ints = new List<int> { 1 };
List<string> strings = ints.ConvertAll<string>(Convert.ToString);

Since the signature of Convert.ToString is also known, you can even eliminate the explicit target type parameter:

var strings = ints.ConvertAll(Convert.ToString);

This works. However, I'd also prefer the lambda-expression, even if ReSharper tells you something different. ReSharper sometimes optimizes too much imho. It prevents developers from thinking about their code, especially in the aspect of readability.

Update

Based on Tim's comment, I will try to explain the difference between lambda and static method group calls in this particular case. Therefor, I first took a look into the mscorlib disassembly to figure out, how int-to-string conversion exactly works. The Int32.ToString method calls an external method within the Number-class of the System namespace:

[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), SecuritySafeCritical]
public string ToString(IFormatProvider provider)
{
    return Number.FormatInt32(this, null, NumberFormatInfo.GetInstance(provider));
}

The static Convert.ToString member does nothing else than calling ToString on the parameter:

[__DynamicallyInvokable]
public static string ToString(int value)
{
    return value.ToString(CultureInfo.CurrentCulture);
}

Technically there would be no difference, if you'd write your own static member or extension, like you did in your question. So what's the difference between those two lines?

ints.ConvertAll<string>(i => i.ToString());
ints.ConvertAll(Convert.ToString);

Also - technically - there is no difference. The first example create's an anonymous method, that returns a string and accepts an integer. Using the integer's instance, it calls it's member ToString. The second one does the same, with the exception that the method is not anonymous, but an integrated member of the framework.

The only difference is that the second line is shorter and saves the compiler a few operations.

But why can't you call the non-static ToString directly?

Let's take a look into the ConvertAll-method of List:

public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter)
{
    if (converter == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.converter);
    }

    List<TOutput> list = new List<TOutput>(this._size);

    for (int i = 0; i < this._size; i++)
    {
        list._items[i] = converter(this._items[i]);
    }

    list._size = this._size;
    return list;
}

The list iteraterates over each item, calls the converter with the item as an argument and copys the result into a new list which it returns in the end.

So the only relation here is your converter that get's called explicitly. If you could pass Int32.ToString to the method, the compiler would have to decide to call this._items[i].ToString() within the loop. In this specific case it would work, but that's "too much intelligence" for the compiler. The type system does not support such code conversions. Instead the converter is an object, describing a method that can be called from the scope of the callee. Either this is an existing static method, like Convert.ToString, or an anonymous expression, like your lambda.

What causes the differences in your benchmark results?

That's hard to guess. I can imagine two factors:

  1. Evaluating lambdas may result in runtime-overhead.
  2. Framework calls may be optimized.

The last point especially means, that the JITer is able to inline the call which results in a better performance. However, those are just assumptions of mine. If anyone could clarify this, I'd appreciate it! :)

Carsten
  • 11,287
  • 7
  • 39
  • 62
  • Yeah, I find method group conversions to be confusing as well. The error messages also aren't that good. – usr May 28 '14 at 10:27
  • For a hundreds of $ product - this is just shame – Shlomi Borovitz May 28 '14 at 10:28
  • 1
    The answer was not meant as bashing, because there are things, ReSharper can do better than VS. However, as of most tools, you need to know how to use it right and when it might be missleading to listen on it. That's what I meant by *it prevents developers from thinking about their code*. Personally I stopped using ReSharper to have more control over my personal code quality. Team feedback is more important than ReSharper feedback. – Carsten May 28 '14 at 10:34
  • So the answer is: _no, it's not possible to use a non-static method that you don't "own"_? I normally agree that readability is most important but sometimes you need the most effcicient way, all the more if the complexity is hidden in (extension) methods. I also don't find `ConvertAll(Convert.ToString)` difficult to understand. Btw, i don't even own resharper, i just wanted to know _how it works_. – Tim Schmelter May 28 '14 at 10:52
  • @TimSchmelter: if *own* means *call a member within scope*, then you're right. That's the "nature" of C# - non-static members can only be called per-instance. Either you use them together with lambdas, or you write your own static (note: also internal extensions are possible!) members that perform the conversion. But what's the difference to using lambdas then? – Carsten May 28 '14 at 11:01
  • @TimSchmelter - lambdas and anonymous methods aren't less efficient. The compiler just create a class with those methods, and create a delegate for them (and that delegate is necessary anyway). About the indirection in calling to `ToString` - that would be inlined, so it is a non-issue too. – Shlomi Borovitz May 28 '14 at 11:21
  • @ShlomiBorovitz: so it doesn't matter how large the list is(in my example) since only one anonymous method is created? But it would matter if this code is executed in a loop. I just wanted to know if it's possible. In this case `System.Convert` does the trick, but just because `int` is supported. So my example was bad. – Tim Schmelter May 28 '14 at 11:23
  • @TimSchmelter - No it would not. The anonymous/lambda method is created once, in compile time. What would slightly matter are variables which used in closure, and declared *inside* the loop. And in any case (anonymous method or not) - a delegate would be created in any iteration of the loop. – Shlomi Borovitz May 28 '14 at 11:27
  • @ShlomiBorovitz: i have tested it with a loop(1M iterations). The method was considerably faster than the lambda(0.25 vs. 0.44 sec). I took the time with `StopWatch` and the simple method i used as test case was: `chars.Count(c => Char.IsDigit(c));`(`Char.IsDigit` in the method version) where `chars` was a `List` with 6 characters. But i still agree that this is premature optimization in most cases. – Tim Schmelter May 28 '14 at 11:40
  • Btw, the difference increases disproportionately, if it's called 10 times more: 1.6 sec.(method) vs. 3.4 sec.(lambda). – Tim Schmelter May 28 '14 at 11:49
  • @TimSchmelter: I've updated the answer... That's all I could find out on this :) – Carsten May 28 '14 at 11:55
  • @TimSchmelter I bet that you compiled in debug mode and let it ran inside the debugger. When testing in release mode, outside of the debugger, they are the same – Shlomi Borovitz May 28 '14 at 12:03
  • @Shlomi: whoops, you're right, shame on me. The difference is clearly smaller in release (between 1.7 vs. 2.3 and 2.0 vs. 2.2 for 10M iterations). – Tim Schmelter May 28 '14 at 12:10
  • @TimSchmelter And if you'll run it without debugging (ctrl+f5), the result would be the same (actually, it may fluctuate with no [preference/trend/bias/I don't know the word] for any of them. – Shlomi Borovitz May 28 '14 at 12:16
  • @Aschratt: many thanks for your efforts. I'll accept the/an answer later. – Tim Schmelter May 28 '14 at 12:17
  • @ShlomiBorovitz: no, the difference remains. I'm writing the result in a text-file and start the app with `ctrl+F5`, first three results: `lambda: 00:00:02.0148356 method: 00:00:01.6915965 lambda: 00:00:02.0950926 method: 00:00:01.6211572 lambda: 00:00:02.0371050 method: 00:00:01.6675659` – Tim Schmelter May 28 '14 at 12:28
  • @TimSchmelter I don't have your code. When I tested it (unfortunately, inside a closed network, so I cannot send the code :( ), there where no deference (I didn't tested `Convert.ToString`, but using a static method, and lambda). – Shlomi Borovitz May 28 '14 at 12:40
  • @TimSchmelter Well, because my result fluctuated around the hundredths of a second, I added 2 zeroes to the loop counter... Now I can really say that they are the same (still fluctuating around the hundredths of a second - which is meaningless for a 20-seconds run) – Shlomi Borovitz May 28 '14 at 12:55
  • @ShlomiBorovitz: nevermind, i've deleted the ideone-link where the method-approach was slower. Let's keep it that way :) – Tim Schmelter May 28 '14 at 13:34
3

You hit the nail on the head yourself:

This works, but it creates an unnecessary anonymous method with the lambda.

You can't do what you're asking for because there is no appropriate method group that you can use so the anonymous method is necessary. It works in that other case because the implicit range variable is passed to the delegate created by the method group. In your case, you need the method to be called on the range variable. It's a completely different scenario.

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
  • I think this hits the point well. Difference between _calling an instance method on instance_ and _passing an instance to the static method_ is the key. – Edin May 28 '14 at 10:28