2013-03-20 17 views
10

При вызове общего метода для хранения объекта иногда приходится обрабатывать определенный тип по-разному. Я знаю, что вы не можете перегружать на основе ограничений, но любая другая альтернатива, похоже, представляет свои проблемы.Перегрузка общих методов

public bool Save<T>(T entity) where T : class 
{ ... some storage logic ... } 

То, что я хотел бы сделать что-то вроде следующего:

public bool Save<SpecificClass>(T entity) 
{ ... special logic ... } 

В прошлом наша команда создала «разовые» методы для сохранения этих классов следующим образом:

public bool SaveSpecificClass(SpecificClass sc) 
{ ... special logic ... } 

Однако, если вы не ЗНАЕЕ, что функция существует, и вы пытаетесь использовать общий (Сохранить), тогда вы можете столкнуться с множеством проблем, которые должен был исправить «одноразовый». Это может быть еще хуже, если появляется новый разработчик, видит проблему с общим и решает, что он исправит его с помощью своей собственной одноразовой функции.

Итак ...

Какие варианты для работы вокруг этого, казалось бы, общего вопроса?

Я посмотрел на и использовал UnitOfWork и сейчас, кажется, только вариант, который фактически решает проблему - но, похоже, атакует муху кувалдой.

+2

В отличие от C++, C# не разрешает специализированную специализацию –

+0

Являются ли эти методы Save() помещены в некоторый легкий вспомогательный класс или в некоторые классы сущностей? Я просто думаю о наследовании, но важно убедиться, что это правильный путь, потому что наследование не всегда правильно используется – sll

+0

Возможный дубликат [Как сделать специализацию шаблона в C#] (http://stackoverflow.com/questions/600978/how -to-do-template-specialization-in-c-sharp) –

ответ

12

Вы можете сделать:

public bool Save<T>(T entity) where T : class 
{ ... some storage logic ... } 

public bool Save(SpecificClass entity) 
{ ... special logic ... } 

Например:

public class SpecificClass 
{ 
} 

public class Specializer 
{ 
    public bool GenericCalled; 
    public bool SpecializedCalled; 

    public bool Save<T>(T entity) where T : class 
    { 
     GenericCalled = true; 
     return true; 
    } 

    public bool Save(SpecificClass entity) 
    { 
     SpecializedCalled = true; 
     return true; 
    } 
} 

public class Tests 
{ 
    [Test] 
    public void TestSpecialization() 
    { 
     var x = new Specializer(); 
     x.Save(new SpecificClass()); 
     Assert.IsTrue(x.SpecializedCalled); 
     Assert.IsFalse(x.GenericCalled); 
    } 
} 
+0

Это сработало, я не могу поверить, что я не думал об этом простое решение ... –

+0

Я не мог заставить его работать: общий версия функции была вызвана в любом случае. – MarkusParker

+0

@MarkusParker Можете ли вы привести пример, поскольку это должно работать нормально, как описано в моем обновлении, которое показывает, что он работает в модульном тесте. Имейте в виду, что переменная, используемая для передачи 'SpecificClass', должна быть типа' SpecificClass', если только она не является классом '' '' '' ', поскольку C# выполняет компиляционный полиморфизм. –

0

Почему используя различные имена для вашего метода?

Смотрите следующее:

public class Entity 
    { 
    } 

    public class SpecificEntity : Entity 
    { 
    } 

    public class Program 
    { 
     public static void Save<T>(T entity) 
      where T : class 
     { 
      Console.WriteLine(entity.GetType().FullName); 
     } 

     public static void Save(SpecificEntity entity) 
     { 
      Console.WriteLine(entity.GetType().FullName); 
     } 

     private static void Main(string[] args) 
     { 
      Save(new Entity());   // ConsoleApplication13.Entity 
      Save(new SpecificEntity()); // ConsoleApplication13.SpecificEntity 

      Console.ReadKey(); 
     } 
    } 
2

Ну поясню C# не позволяет специализации шаблона, кроме как через наследования, как это:

interface IFoo<T> { } 
class Bar { } 

class FooBar : IFoo<Bar> { } 

По крайней мере, он не поддерживает это во время компиляции. Однако вы можете использовать RTTI, чтобы делать то, что вы пытаетесь достичь:

public bool Save<T>(T entity) 
{ 
    // Check if "entity" is of type "SpecificClass" 
    if (entity is SpecificClass) 
    { 
     // Entity can be safely casted to "SpecificClass" 
     return SaveSpecificClass((SpecificClass)entity); 
    } 

    // ... other cases ... 
} 

is expression очень удобно делать проверки типов во время выполнения. Он работает аналогичен следующий код:

if (entity.GetType() == typeof(SpecificClass)) 
    // ... 

EDIT: Это довольно общее для неизвестных типов использовать следующий шаблон:

if (entity is Foo) 
    return DoSomethingWithFoo((Foo)entity); 
else if (entity is Bar) 
    return DoSomethingWithBar((Bar)entity); 
else 
    throw new NotSupportedException(
     String.Format("\"{0}\" is not a supported type for this method.", entity.GetType())); 

EDIT 2: Как и другие ответы предполагают перегрузка метод с SpecializedClass вам нужно позаботиться, если вы работаете с полиморфизмом.Если вы используете интерфейсы для своего репозитория (который на самом деле является хорошим способом для создания шаблона репозитория), случаются случаи, когда перегрузка приведет к тому, что вы ошибаетесь в вызове метода get, независимо от того, передаете ли вы объект SpecializedClass к интерфейсу:

interface IRepository 
{ 
    bool Save<T>(T entity) 
     where T : class; 
} 

class FooRepository : IRepository 
{ 
    bool Save<T>(T entity) 
    { 
    } 

    bool Save(Foo entity) 
    { 
    } 
} 

Это работает, если вы напрямую позвонить FooRepository.Save с экземпляром Foo:

var repository = new FooRepository(); 
repository.Save(new Foo()); 

Но это не работает, если вы звоните интерфейс (например, если вы используете шаблоны для создать хранилище репозитория):

IRepository repository = GetRepository<FooRepository>(); 
repository.Save(new Foo()); // Attention! Call's FooRepository.Save<Foo>(Foo entity) instead of FooRepository.Save(Foo entity)! 

Используя RTTI, существует только один метод Save, и со всеми будет все в порядке.

+0

Что не так с перегрузкой, которая принимает определенный тип типа' Save (SpecificClass entity) ' –

+0

В этом нет ничего плохого, но это все еще перегрузка, а не специализация. ;) – Carsten

+0

Вопрос * есть * о «перегрузке» ... :) –

3

Поскольку функция и оператор перегрузки, связанные с дженерики связаны во время компиляции, а не во время выполнения, если код имеет два метода:

public bool Save<T>(T entity) ... 
public bool Save(SomeClass entity) ... 

то код, который пытается вызвать Save(Foo) где Foo является переменной некоторых общий тип всегда будет называть прежнюю перегрузку, даже если общий тип SomeClass. Мое предложение разрешить это было бы определить общий интерфейс ISaver<in T> с неосновным методом DoSave(T param). Пусть класс, который предоставляет метод Save, реализует все соответствующие общие интерфейсы для типов, которые он может обрабатывать. Затем попробуйте применить метод Save<T> объекта: this - ISaver<T>. Если литье выполнено успешно, используйте полученный результат ISaver<T>; в противном случае выполните общее сохранение. При условии, что объявление типа класса содержит список всех соответствующих интерфейсов для типов, которые он может сохранить, этот подход будет отправлять вызовы Save надлежащим методам.

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