2016-12-23 2 views
3

Я использовал шаблоны на C++, но на самом деле не пытался слишком часто сталкиваться с генераторами C# в прошлом. Вот упрощенная урезанная версия о том, что я пытаюсь сделать (это было бы возможно в C++):Перехват методов вызова класса класса C# с помощью примитивов

class DoesStuffWithPrimatives 
{ 
    public void DoStuff(double value) { } 
    public void DoStuff(string value) { } 
    public void DoStuff(int value) { } 
    public void DoStuff(uint value) { } 
    // etc... 
} 

class GenericBase<T> 
{ 
    private readonly T _testValue; 
    private DoesStuffWithPrimatives _doesStuff = new DoesStuffWithPrimatives(); 
    public GenericBase(T testValue) 
    { 
     _testValue = testValue; 
    } 

    public void DoStuff() 
    { 
     _doesStuff.DoStuff(_testValue); 
    } 
} 

class DoubleContrete : GenericBase<double> 
{ 
    public DoubleContrete() : base(1.54545487) 
    { 
    } 
} 

class IntConrete : GenericBase<int> 
{ 
    public IntConrete() : base(80085) 
    { 
    } 
} 

Я получаю компиляции ошибки (в методе DoStuff() на GenericBase<T>):

Ошибка CS1503 Аргумент 1: не удается преобразовать из «T» в «двойной»

Почему не компилятор решительность, какой из DoesStuffWithPrimatives.DoStuff(…) перегрузок называть !?

ответ

1

С помощью генераторов лучшее, что может сделать компилятор, это предположить, что параметр типа (T в этом случае) может быть любым типом с базой того, что вы указали. Поскольку вы не указали базу, компилятор рассматривает T как все, что наследует от Object, что буквально что угодно.

компилятор не может решить, что ваш T является двойной, потому что не имеет смысла для каждого типа T, что неdouble. Возьмем, к примеру, следующий общий метод:

public void DoStuff<T>(T param) 
{ 
    DoStuffWithDouble(param); 
} 

Когда T вдвое, то это будет работать нормально, как вы можете просто заменить double для T:

public void DoStuff(double param) 
{ 
    DoStuffWithDouble(param); // param is a double, so no problem 
} 

Однако T может быть что-то иначе как, скажем, List. В этом случае, этот код не будет составлять:

public void DoStuff(List param) 
{ 
    DoStuffWithDouble(param); // param is not double, this wouldn't compile 
} 

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

Вы можете использовать его, конечно, а также выполнить проверки типа объекта.

public void DoStuff<T>(T param) 
{ 
    if (param is double) 
     // Only runs if T is confirmed to be a double, so no chance for errors 
     DoStuffWithDouble((double)param); 
} 
+0

Очистить ответ. Было бы неплохо, если бы вы обеспечили простое использование параметра типа в родовом классе, чтобы позволить ему использовать методы этого типа. –

+0

Кроме того, что-то интересное, если один добавить 'DoStuff (значение объекта)', то ошибка будет идти, однако, код будет запеченным использовать «DoStuff (значение объекта)' независимо от того, что указано в параметре типа. –

+1

Вы не можете использовать 'T' для' double'. Я пробовал код, и он не компилируется. – Sweeper

0

Причина, почему код не работает, что T не ограничивается фиксированным набором типов. T может быть любой тип: структура, класс, делегат, интерфейс ...

Когда вы сделаете это:

public void DoStuff() 
{ 
    _doesStuff.DoStuff(_testValue); 
} 

Это, конечно, не будет компилироваться, так как _testValue может быть что угодно, IEnumerable, Button, XmlSerializer, Dictionary<int, int>, CultureInfo или даже некоторые типы, которые вы создали сами, просто чтобы назвать несколько экстремальных. Очевидно, вы никогда не сможете передать CultureInfo методу, который принимает double или int.

Сообщение об ошибке - это только способ компилятора: T может быть любого типа, не обязательно double или int.

Одним из решений этой проблемы просто, просто не использовать дженерики на всех! На мой взгляд, я не думаю, что вы должны использовать дженерики в этой ситуации.

Дженерики следует использовать, когда вы заботитесь немного о (или не волнует вообще), какой тип используется. Например, List<T> не заботится о том, какой тип он хранит. Он работает с любым типом. Некоторые другие классы/интерфейсы/методы работают с типами, которые имеют определенные атрибуты: реализовать определенные интерфейсы, имеет конструктор по умолчанию, является классом и т. Д. Поэтому мы имеем общие ограничения типа. Однако всегда существует бесконечный набор типов, который выполняет эти ограничения. Вы всегда можете создать тип, который реализует определенные интерфейсы или имеет конструктор по умолчанию. Не существует общих ограничений, которые ограничивают общий тип конечным числом типов, таких как «примитивы», насколько мне известно.

0

Как указывалось выше, компилятор не может знать наверняка, что T является допустимым типом для любого из перегрузкам. Однако можно использовать отражение, чтобы получить правильный метод. Без надлежащего управления это может привести к ошибкам во время выполнения, и некоторые издержки. Накладные расходы могут быть в значительной степени отрицательными, если возможно иметь статическую переменную. В примере ниже «dostuffer» Action<T> будет определяться один раз для каждого типа, когда он используется:

class GenericBase<T> 
{ 
    private readonly T _testValue; 

    public GenericBase(T testValue) 
    { 
     _testValue = testValue; 
    } 


    static readonly DoesStuffWithPrimatives _doesStuff = new DoesStuffWithPrimatives(); 
    static readonly Action<T> dostuffer = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>),_doesStuff, 
     typeof(DoesStuffWithPrimatives).GetMethod(nameof(DoesStuffWithPrimatives.DoStuff), new[]{typeof(T)})); 


    public void DoStuff() 
    { 
     dostuffer(_testValue); 
    } 
} 

DoesStuffWithPrimatives Если класс не может быть статическим (если он может состоять из нескольких различных переменных), статических переменная должна быть информация метод и экземпляр конструктор GenericBase должен обрабатывать CreateDelegate, используя DoesStuffWithPrimatives переменной

Смежные вопросы