Подтвердить что ты не робот

Неоднозначный вызов между двумя универсальными методами расширения С#, где T: class и другие, где T: struct

Рассмотрим два метода расширения:

public static T MyExtension<T>(this T o) where T:class
public static T MyExtension<T>(this T o) where T:struct

И класс:

class MyClass() { ... }

Теперь вызовите метод расширения в экземпляре указанного класса:

var o = new MyClass(...);
o.MyExtension(); //compiler error here..
o.MyExtension<MyClass>(); //tried this as well - still compiler error..

Компилятор говорит, что вызов метода является неоднозначным вызовом, когда я вызываю его в классе. Я бы подумал, что он может определить, какой метод расширения вызывать, поскольку MyClass - это класс, а не структура?

4b9b3361

Ответ 1

EDIT: теперь я написал об этом более подробно.


Мое оригинальное (и я теперь считаю неверным) мысль: общие ограничения не учитываются во время разрешения перегрузки и типа вывода - они используются только для проверки результата разрешения перегрузки.

РЕДАКТИРОВАТЬ: Хорошо, после многого на этом, я думаю, что я там. В основном моя первая мысль была почти правильной.

Ограничения общего типа действуют только для удаления методов из набора кандидатов в очень ограниченном наборе обстоятельств... в частности, только в том случае, когда тип самого параметра является общим; а не только параметр типа, а общий тип, который использует параметр типового типа. В этот момент это ограничения на параметры типа общего типа, которые проверяются, а не ограничения на параметры типа общего метода, который вы вызываете.

Например:

// Constraint won't be considered when building the candidate set
void Foo<T>(T value) where T : struct

// The constraint *we express* won't be considered when building the candidate
// set, but then constraint on Nullable<T> will
void Foo<T>(Nullable<T> value) where T : struct

Поэтому, если вы попытаетесь вызвать Foo<object>(null), указанный выше метод не будет частью набора кандидатов, потому что Nullable<object> value не удовлетворяет ограничениям Nullable<T>. Если есть другие применимые методы, вызов все равно может быть успешным.

Теперь в приведенном выше случае ограничения одинаковы... но они не обязательно должны быть. Например, рассмотрим:

class Factory<TItem> where TItem : new()

void Foo<T>(Factory<T> factory) where T : struct

Если вы попытаетесь вызвать Foo<object>(null), этот метод по-прежнему будет частью набора кандидатов, потому что когда TItem равно object, ограничение, выраженное в Factory<TItem>, сохраняется, и это то, что проверяется при создании кандидат установлен. Если это окажется лучшим методом, он позже завершит проверку, ближе к концу 7.6.5.1:

Если лучшим методом является общий метод, аргументы типа (поставляемые или выводимые) проверяются на ограничения (§4.4.4), объявленные в общем методе. Если какой-либо аргумент типа не удовлетворяет соответствующему ограничению (ограничениям) в параметре типа, возникает ошибка времени привязки.

Eric сообщение в блоге содержит более подробную информацию об этом.

Ответ 2

Эрик Липперт объясняет лучше, чем когда-либо, здесь.

Я сам наткнулся на это. Мое решение было

public void DoSomthing<T> (T theThing){
    if (typeof (T).IsValueType)
        DoSomthingWithStruct (theThing);
    else
        DoSomthingWithClass (theThing);  
}

// edit - seems I just lived with boxing

public void DoSomthingWithStruct (object theThing)
public void DoSomthingWithClass(object theThing)

Ответ 3

Я нашел этот "интересный" странный способ сделать это в .NET 4.5, используя значения параметров по умолчанию:) Возможно, это более полезно для образовательных\спекулятивных целей, чем для реального использования, но я хотел бы показать это:

/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class MagicValueType<TBase>
    where TBase : struct
{
}

/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class MagicRefType<TBase>
    where TBase : class
{
}

struct MyClass1
{
}

class MyClass2
{
}

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

    // More magic, other pink unicorns and rainbows.
    public static T Test<T>(this T t, MagicValueType<T> x = null)
        where T : struct
    {
        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());

        Console.ReadLine();
    }
}