С# - использование методов расширения для обеспечения реализации интерфейса по умолчанию

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

Скажи:

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, использовать метод расширения по умолчанию, который возвращает пустую строку, но собака и любое животное, которое имеет явную реализацию, возвращают свою собственную реализацию, такую ​​как "Кора".

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

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

Ответ 1

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

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) и полностью избавиться от метода расширения. В зависимости от вашего фактического варианта использования это может быть предпочтительнее предыдущей опции.

Ответ 2

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

Например:

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

public class Porcupine : IAnimal
{
}

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

Ответ 3

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

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 ""; } }
}