Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Extension Method Ambiguity

Tags:

c#

.net

generics

I have two interfaces defined:

// IVector.cs
public interface IVector
{
    int Size { get; }

    float this[int index] { get; set; }
}

// IMatrix.cs
public interface IMatrix
{
    int Size { get; }

    float this[int row, int column] { get; set; }
}

As well as extension methods for those interfaces

// VectorExtensions.cs
public static T Add<T>(this T vector, T value) where T : struct, IVector
{
    var output = default(T);

    for (int i = 0; i < output.Size; i++)
        output[i] = vector[i] + value[i];

    return output;
}

// MatrixExtensions.cs
public static T Add<T>(this T matrix, T value) where T : struct, IMatrix
{
    var output = default(T);

    for (int i = 0; i < output.Size; i++)
        for (int j = 0; j < output.Size; j++)
            output[i, j] = vector[i, j] + value[i, j];

    return output;
}

All the types are in the same namespace.

For some reason, when calling Add() on something derived from IVector, the compiler can't determine whether to use the definition in the MatrixExtensions class or the VectorExtensions class. Moving one of the extension classes to a different namespace stops the errors... but I kinda want them in the same namespace :D

Why is this happening?

EDIT: (I can't believe I forgot to add this)
What should I do to work around this?

like image 588
YellPika Avatar asked May 07 '11 03:05

YellPika


People also ask

What are extension methods explain with an example?

An extension method is actually a special kind of static method defined in a static class. To define an extension method, first of all, define a static class. For example, we have created an IntExtensions class under the ExtensionMethods namespace in the following example.

What is correct extension method?

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.

What is extension method in MVC?

What is extension method? Extension methods in C# are methods applied to some existing class and they look like regular instance methods. This way we can "extend" existing classes we cannot change. Perhaps the best example of extension methods are HtmlHelper extensions used in ASP.NET MVC.

What is extension method in OOP?

In object-oriented computer programming, an extension method is a method added to an object after the original object was compiled. The modified object is often a class, a prototype or a type. Extension methods are features of some object-oriented programming languages.


2 Answers

I just found a curious way that works in .NET 4.5 using a trick with Default Parameters.

/// <summary>Simple base class. Can also be an interface, for example.</summary>
public abstract class MyBase1
{
}

/// <summary>Simple base class. Can also be an interface, for example.</summary>
public abstract class MyBase2
{
}

/// <summary>Concrete class 1.</summary>
public class MyClass1 :
    MyBase1
{
}

/// <summary>Concrete class 2.</summary>
public class MyClass2 :
    MyBase2
{
}

/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class Magic<TBase, TInherited>
    where TInherited : TBase
{
    private Magic()
    {
    }
}

// Extensions
public static class Extensions
{
    // Rainbows and pink unicorns happens here.
    public static T Test<T>(this T t, Magic<MyBase1, T> x = null)
        where T : MyBase1
    {
        Console.Write("1:" + t.ToString() + " ");
        return t;
    }

    // More magic, other pink unicorns and rainbows.
    public static T Test<T>(this T t, Magic<MyBase2, T> x = null)
        where T : MyBase2
    {
        Console.Write("2:" + t.ToString() + " ");
        return t;
    }
}

class Program
{
    static void Main(string[] args)
    {

        MyClass1 t1 = new MyClass1();
        MyClass2 t2 = new MyClass2();

        MyClass1 t1result = t1.Test();
        Console.WriteLine(t1result.ToString());

        MyClass2 t2result = t2.Test();
        Console.WriteLine(t2result.ToString());
    }
}

I am curious to see if this works on MONO compiler (Mcs) Someone wanna try? :)

like image 146
Salvatore Previti Avatar answered Nov 06 '22 00:11

Salvatore Previti


You have two extension method that each have the same signature.

// VectorExtensions.cs
public static T Add<T>(this T vector, T value)

// MatrixExtensions.cs 
public static T Add<T>(this T matrix, T value)

Yes, you have provided constraints in your code, but constraints are not part of the signature. So you have two method with the same signature, neither method is therefore better than the other, and you have an ambiguity problem.

The reason why moving one of the static extension method classes into a different namespace has a different result is that the compiler will look for extension method matches first within the closest containing namespace before expanding the search outward. (See: Section 7.5.5.2 [below] of the C# language specification.) If you move MatrixExtensions, for example, into a different namespace, now extension method invocations inside the original namespace will unambiguously resolve to the VectorExtensions method, as it is the closest in terms of namespaces. However, this doesn't completely resolve your problem. Because you could still have IMatrix trying to use the VectorExtensions implementation if it's the closest extension method because, again, constraints are not part of the signature.

For your convenience, the language specification.

7.5.5.2 Extension method invocations

In a method invocation (§7.5.5.1) of one of the forms

expr . identifier ( )

expr . identifier ( args )

expr . identifier < typeargs > ( )

expr . identifier < typeargs > ( args )

if the normal processing of the invocation finds no applicable methods, an attempt is made to process the construct as an extension method invocation. The objective is to find the best type-name C, so that the corresponding static method invocation can take place:

C . identifier ( expr )

C . identifier ( expr , args )

C . identifier < typeargs > ( expr )

C . identifier < typeargs > ( expr , args )

The search for C proceeds as follows:

  • Starting with the closest enclosing namespace declaration, continuing with each enclosing namespace declaration, and ending with the containing compilation unit, successive attempts are made to find a candidate set of extension methods:
    • If the given namespace or compilation unit directly contains non-generic type declarations Ci with extension methods Mj that have the name identifier and are accessible and applicable with respect to the desired static method invocation above, then the set of those extension methods is the candidate set.
    • If namespaces imported by using namespace directives in the given namespace or compilation unit directly contain non-generic type declarations Ci with extension methods Mj that have the name identifier and are accessible and applicable with respect to the desired static method invocation above, then the set of those extension methods is the candidate set.
  • If no candidate set is found in any enclosing namespace declaration or compilation unit, a compile-time error occurs.
  • Otherwise, overload resolution is applied to the candidate set as described in (§7.4.3). If no single best method is found, a compile-time error occurs.
  • C is the type within which the best method is declared as an extension method. Using C as a target, the method call is then processed as a static method invocation (§7.4.4). The preceding rules mean that instance methods take precedence over extension methods, that extension methods available in inner namespace declarations take precedence over extension methods available in outer namespace declarations, and that extension methods declared directly in a namespace take precedence over extension methods imported into that same namespace with a using namespace directive
like image 42
Anthony Pegram Avatar answered Nov 05 '22 23:11

Anthony Pegram