Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static method and extension method with same name

I created extension method:

public static class XDecimal
{
    public static decimal Floor(
        this decimal value,
        int precision)
    {
        decimal step = (decimal)Math.Pow(10, precision);
        return decimal.Floor(step * value) / step;
    }
}

Now I try to use it:

(10.1234m).Floor(2)

But compiler says Member 'decimal.Floor(decimal)' cannot be accessed with an instance reference; qualify it with a type name instead. I understand there is static decimal.Floor(decimal) method. But it has different signature. Why compiler is unable to choose correct method?

like image 675
Denis Avatar asked Dec 16 '15 16:12

Denis


People also ask

Can a static and instance method have same name?

You can have static and instance method with the same name, as long as their declaration differs in the number or type of parameters. It's the same rule on how you can have two instance methods with the same name in a class.

Can extension methods be static?

Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are static methods, but they're called as if they were instance methods on the extended type.

Why do extension methods need to be in a static class?

Instance level extension methods are useful because they work on the current state of a type instance. The static method has no context, so it would not provide any utility over a static method defined elsewhere aside from logical grouping (i.e., defining a String.

What is the difference between a static method and an extension method?

The only difference between a regular static method and an extension method is that the first parameter of the extension method specifies the type that it is going to operator on, preceded by the this keyword.


2 Answers

You have two good and correct answers here, but I understand that answers which simply quote the specification are not always that illuminating. Let me add some additional details.

You probably have a mental model of overload resolution that goes like this:

  • Put all the possible methods in a big bucket -- extension methods, static methods, instance methods, etc.
  • If there are methods that would be an error to use, eliminate them from the bucket.
  • Of the remaining methods, choose the unique one that has the best match of argument expressions to parameter types.

Though this is many people's mental model of overload resolution, regrettably it is subtly wrong.

The real model -- and I will ignore generic type inference issues here -- is as follows:

  • Put all the instance and static methods in a bucket. Virtual overrides are not counted as instance methods.
  • Eliminate the methods that are inapplicable because the arguments do not match the parameters.

At this point we either have methods in the bucket or we do not. If we have any methods in the bucket at all then extension methods are not checked. This is the important bit right here. The model is not "if normal overload resolution produced an error then we check extension methods". The model is "if normal overload resolution produced no applicable methods whatsoever then we check extension methods".

If there are methods in the bucket then there is some more elimination of base class methods, and finally the best method is chosen based on how well the arguments match the parameters.

If this happens to pick a static method then C# will assume that you meant to use the type name and used an instance by mistake, not that you wish to search for an extension method. Overload resolution has already determined that there is an instance or static method whose parameters match the arguments you gave, and it is going to either pick one of them or give an error; it's not going to say "oh, you probably meant to call this wacky extension method that just happens to be in scope".

I understand that this is vexing from your perspective. You clearly wish the model to be "if overload resolution produces an error, fall back to extension methods". In your example that would be useful, but this behaviour produces bad outcomes in other scenarios. For example, suppose you have something like

mystring.Join(foo, bar);

The error given here is that it should be string.Join. It would be bizarre if the C# compiler said "oh, string.Join is static. The user probably meant to use the extension method that does joins on sequences of characters, let me try that..." and then you got an error message saying that the sequence join operator -- which has nothing whatsoever to do with your code here -- doesn't have the right arguments.

Or worse, if by some miracle you did give it arguments that worked but intended the static method to be called, then your code would be broken in a very bizarre and hard-to-debug way.

Extension methods were added very late in the game and the rules for looking them up make them deliberately prefer giving errors to magically working. This is a safety system to ensure that extension methods are not bound by accident.

like image 144
Eric Lippert Avatar answered Sep 28 '22 06:09

Eric Lippert


The process of deciding on which method to call has lots of small details described in the C# language specification. The key point applicable to your scenario is that extension methods are considered for invocation only when the compiler cannot find a method to call among the methods of the receiving type itself (i.e. the decimal).

Here is the relevant portion of the specification:

The set of candidate methods for the method invocation is constructed. For each method F associated with the method group M:

  • If F is non-generic, F is a candidate when:

  • M has no type argument list, and

  • F is applicable with respect to A (§7.5.3.1).

According to the above, double.Floor(decimal) is a valid candidate.

If the resulting set of candidate methods is empty, then further processing along the following steps are abandoned, and instead an attempt is made to process the invocation as an extension method invocation (§7.6.5.2). If this fails, then no applicable methods exist, and a binding-time error occurs.

In your case the set of candidate methods is not empty, so extension methods are not considered.

like image 33
Sergey Kalinichenko Avatar answered Sep 28 '22 07:09

Sergey Kalinichenko