2013-12-06 3 views
10

Я просто изучаю методы расширения C# и задавался вопросом, могу ли я использовать его для предоставления реализации по умолчанию для интерфейса.C# - использование методов расширения для обеспечения реализации интерфейса по умолчанию

Скажи:

public interface Animal { 
    string MakeSound(); 
} 

public static string MakeSound(this Animal) { 
    return ""; 
} 

Тогда

public class Dog : Animal { 
    string MakeSound() { 
     return "Bark"; 
    } 
} 

public class Porcupine : Animal { 
} 

И последнее:

Animal dog = new Dog(); 
Animal porcupine = new Porcupine(); 

Print(dog.MakeSound()); 
Print(porcupine.MakeSound()); 

Я хотел бы дикобраза и любых других животных, которые явно не реализованы MakeSound использовать по умолчанию, который возвращает пустую строку, но собаку и любое животное, которое делает es имеют явную реализацию, возвращают свою собственную реализацию, такую ​​как «Bark».

Так что мои вопросы: 1. Является ли это выполнимым? 2. Если нет, есть ли другой способ реализовать поведение по умолчанию для интерфейса?

Абстрактный класс вместо интерфейса не является вариантом, поскольку C# не поддерживает множественное наследование, а мои классы наследуют поведение другого класса.

+6

Нет. Метод расширения не может использоваться для удовлетворения контрактных контрактов. –

+0

Хорошо, стоило того ... Любые другие предложения о том, как это сделать, учитывая, что я не хочу переписывать поведение по умолчанию и тот факт, что я не могу использовать абстрактный класс? – tbkn23

+1

Почему вам не нужен абстрактный класс? Аннотация - это просто интерфейс с некоторыми из уже реализованных методов. Затем вы просто переопределяете методы для тех подклассов, которые должны использовать другую реализацию для базового класса. – Trent

ответ

11

я обычно рекомендую базовый класс, тем не менее, если это, вы можете сделать что-то вроде этого:

public interface IAnimal { } 

public interface INoisyAnimal : IAnimal { 
    string MakeSound(); 
} 

public static class AnimalExtensions { 
    public static string MakeSound(this IAnimal someAnimal) { 
     if (someAnimal is INoisyAnimal) { 
      return (someAnimal as INoisyAnimal).MakeSound(); 
     } 
     else { 
      return "Unknown Noise"; 
     } 
    } 
} 

public class Dog : INoisyAnimal { 
    public string MakeSound() { 
     return "Bark"; 
    } 
} 

public class Porcupine : IAnimal { } 

Это делает каждый IAnimalвзгляд вроде INoisyAnimal, даже если это не совсем один. Например:

IAnimal dog = new Dog(); 
IAnimal porcupine = new Porcupine(); 

Console.WriteLine(dog.MakeSound());   // bark 
Console.WriteLine(porcupine.MakeSound());  // Unknown Noise 

Однако это еще не реальная реализация интерфейса. Обратите внимание, что несмотря на видимость

Console.WriteLine(porcupine is INoisyAnimal); // false 

Другим вариантом может быть, чтобы создать оболочку для расширения вашего базового класса, когда новые функции необходимы:

public class NoisyAnimalWrapper : INoisyAnimal { 
    private readonly IAnimal animal; 
    public NoisyAnimalWrapper(IAnimal animal) { 
     this.animal = animal; 
    } 

    public string MakeSound() { 
     return "Unknown Noise"; 
    } 
} 

public static class AnimalExtensions { 
    public static INoisyAnimal Noisy(this IAnimal someAnimal) { 
     return someAnimal as INoisyAnimal ?? 
       new NoisyAnimalWrapper(someAnimal); 
    } 
} 

Тогда вы можете создать INoisyAnimal из любого IAnimal когда это вам необходимо:

INoisyAnimal dog = new Dog(); 
INoisyAnimal porcupine = new Porcupine().Noisy(); 

Console.WriteLine(dog.MakeSound());   // bark 
Console.WriteLine(porcupine.MakeSound());  // Unknown Noise 

Вы также можете сделать общий оберток (например, NoisyAnimal<T> where T : IAnimal, new) и вообще избавиться от метода расширения. В зависимости от вашего фактического варианта использования это может быть предпочтительнее предыдущей опции.

+0

Я определенно согласен с тем, что базовый класс лучше, когда это применимо. Что касается вашего первого предложения, поскольку методы расширения оцениваются во время компиляции, не будет ли 'dog.MakeSound()' возвращать поведение по умолчанию, даже если это реализация 'INoisyAnimal'? Компилятор не знает, что собака имеет тип 'INoisyAnimal', и, насколько это известно, будет вызываться метод расширения IAnimal.MakeSound(). Я ошибаюсь? – tbkn23

+0

@ tbkn23 Правильно, поэтому вам нужно проверить время выполнения внутри метода расширения. В противном случае '((IAnimal) новый Dog()). MakeSound()' будет возвращать 'Unknown Noise'. –

+0

Ну, я не читал реализацию по умолчанию :) Спасибо – tbkn23

0

Как насчет чего-то подобного? Это позволяет вам избежать базового класса, и вы можете делать то, что имеете в виду, не так ли?

public interface Animal 
{ 
    // Fields 
    string voice { get; } 
} 

public static class AnimalHelper 
{ 
    // Called for any Animal 
    public static string MakeSound(this Animal animal) 
    { 
     // Common code for all of them, value based on their voice 
     return animal.voice; 
    } 
} 

public class Dog : Animal 
{ 
    public string voice { get { return "Woof!"; } } 
} 

public class Purcupine : Animal 
{ 
    public string voice { get { return ""; } } 
} 
2

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

Например:

public class Dog : IAnimal, INoisy 
{ 
    public string MakeSound() 
    { 
     return "Bark"; 
    } 
} 

public class Porcupine : IAnimal 
{ 
} 

Тогда вы будете вызывать только MakeSound или объекты, которые на самом деле шумно.

+0

Сегрегация интерфейса может усложнить дизайн обертки. Учитывая интерфейс со многими необязательными методами и функциями 'canX', легко написать объект-оболочку, который сможет выставлять функции, поддерживаемые обернутым объектом, не требуя, чтобы обернутый объект мог поддерживать функции, которым может обладать пользователь обертки. В отличие от этого, учитывая объект, реализующий комбинацию интерфейсов, нет хорошего способа сделать оболочку реализацией той же комбинации интерфейсов. – supercat

+0

@supercat, конечно, спасибо за ваш вклад. Это заставило меня задуматься. Не могли бы мы попытаться сделать это, не нарушив L в SOLID? Я думал, если у нас есть список, мы могли бы иметь «animals.OfType () .ForEach (a => a.MakeSound())'. Я видел некоторые очень ужасные методы, которые пытаются понять, каковы основные реализации, и они склонны к урагану при каждом изменении. Мне любопытно, как и в самом деле была проблема, с которой разработчик был представлен, поскольку на самом деле это могло потребовать совершенно иного решения, чем то, что предлагалось в первую очередь. –

+0

Подход, который я хотел бы видеть, был бы для рамки, позволяющей каждому интерфейсу X указывать, что он должен рассматриваться как реализованный всеми реализациями какого-либо другого интерфейса Y, так что загрузка любых реализаций другого интерфейса Y, t реализация X будет автоматически генерировать для каждого члена X элемент, который привязывается к статическому члену X. Тогда интерфейс 'INoisyAnimal: Animal' может включать свойство« CanMakeSound »с реализацией по умолчанию, которая возвращает' false', а функция 'MakeSound 'с реализацией по умолчанию, которая генерирует исключение. – supercat

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