简体   繁体   中英

C# - using extension methods to provide default interface implementation

I'm just learning about C# extension methods, and was wondering if I can use it to provide a default implementation for an interface.

Say:

public interface Animal {
    string MakeSound();
}

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

Then

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

public class Porcupine : Animal {
}

And last:

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

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

I'd like the porcupine and any other animals that have not explicitly implemented MakeSound to use the default extension method that returns an empty string, but dog and any animal that does have an explicit implementation return its own implementation such as "Bark".

So my questions: 1. Is this doable? 2. If not, is there any other way to implement default behavior for an interface?

Abstract class instead of interface is not an option because C# doesn't support multiple inheritance and my classes are inheriting behavior of another class.

I'd generally recommend a base class, however, if that's out, you can do something like this:

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 { }

This makes every IAnimal look like a INoisyAnimal even if it isn't really one. For example:

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

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

However, this still isn't an actual implementation of the interface. Notice that despite appearances

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

Another option might be to create a wrapper to extend your base class when new functionality is needed:

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

Then you can create a INoisyAnimal from any IAnimal whenever you need to:

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

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

You could also make the wrapper generic (eg NoisyAnimal<T> where T : IAnimal, new ) and get rid of the extension method altogether. Depending on your actual use case, this may be preferable to the previous option.

I don't know exactly what is your real case or if you are just experimenting but, if only some animals are noisy, then it might a good case for Interface segregation .

For example:

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

public class Porcupine : IAnimal
{
}

Then, you will only call MakeSound or objects that are actually noisy.

How about something like this? It allows you to avoid having a base class, and you can do what you had in mind, right?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM