Click here to Skip to main content
15,936,247 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
See more:
I'm seeking a way to integrate additional functionality into various classes in C#. Focusing on a default implementation for interface methods was a good idea.

Consider the following class structure:

Class A is a base for classes B, C, and D.
Class E is a base for classes F, G, and H.
Class O is a base for classes P, Q, and R.

Among these, classes B, F, and P must implement the interface IDoable, which includes a method X that should be consistent across all implementing classes.

Here's a simplified example:

C#
public class A
{ }

public class B : A, IDoable
{ }

public class C : A
{ }

public class D : A
{ }

public class E
{ }

public class F : E, IDoable
{ }

public class G : E
{ }

public class H : E
{ }

public class O
{ }

public class P : O, IDoable
{ }

public class Q : O
{ }

public class R : O
{ }

public interface IDoable
{
    // Yuck, that smells. Default implementation.
    string X() => "I want to implement method X only once";
}

public class Test
{
    public void Run()
    {
        string bx = new B().X();
        string fx = new F().X();
        string px = new P().X();
        
        string result = bx == fx && bx == px ? "Same :)" : "Various :("; 
    }
}


However, there's a debate regarding whether using default implementations for interface methods is advisable in C#. Are there alternative approaches to achieve this functionality without relying on default implementations? Or is using default implementations acceptable in this scenario?

Note: A, E, and O have no shared characteristics and are not related.

What I have tried:

Firstly, I refrained from implementing X directly in classes B, F, and P to avoid violating the DRY principle. Given that X is meant to be identical across these classes, this approach seemed impractical, especially if X is lengthy.

Secondly, I created an extension method, which worked well for providing the X functionality:
C#
public static class DoableExtensions
{
    public static string X(this IDoable doable)
    {
        return "I want to implement method X only once";
    }
}

However, this approach resulted in an empty IDoable interface, which violates the "CA1040: Avoid empty interfaces" rule.

Lastly, I considered the Decorator pattern, but I'm unsure how to implement it efficiently while maintaining adherence to the DRY principle. But it's possible this approach is the key solution.
Posted

1 solution

Keeping the question theoretical makes it very difficult to give a concrete answer without knowing the situation. This sounds like your teacher, or an interviewer for a job, is trying to give you a question with no one right answer to see how you would respond.

Looking at the options presented, the decorator pattern solution suggests that all 3 base classes have a common base class or interface. So the empty interface with an extension method would be the likely solution. Reason: you can't inherit more than one concrete (base/abstract) class but you can implement multiple (implicit/explicit) interfaces.
 
Share this answer
 
Comments
RuthWa 12-Feb-24 18:12pm    
Okay. I made this code myself. The thing is, I find default implementations very useful. So I don't understand why lots of people hate this feature, especially if there are no alternatives to add some additional functionalities to any class, even if it is derived from another one. Actually, those alternatives exist, but no one that doesn't break rules such as DRY principle or CA1040 which says "Avoid empty interfaces".

"So the empty interface with an extension method would be the likely solution." - Maybe, but it is a violation of CA1040 rule.

Anyway, thanks for the answer.
Graeme_Grant 12-Feb-24 18:32pm    
Those rules are guidelines. There are instances/exceptions, like this one that you have created for yourself. You can mark specific rules to be ignored to stop the warning(s) or compiler errors if set to strict.

FYI: CA1040: Avoid empty interfaces (code analysis) - .NET | Microsoft Learn[^] states: "By default, this rule only looks at externally visible interfaces, but this is configurable."

Then states:
"If your design includes empty interfaces that types are expected to implement, you are probably using an interface as a marker or a way to identify a group of types. If this identification will occur at run time, the correct way to accomplish this is to use a custom attribute. Use the presence or absence of the attribute, or the properties of the attribute, to identify the target types. If the identification must occur at compile time, then it is acceptable to use an empty interface."

Trouble is, attributes add extra code and reflection overhead. Are Attributes a cleaner "looking" solution? Yes. Also, Attributes can be used anywhere and require strict checking to ensure compliance, ie: extra coding overhead.

This is your get out-of-jail comment (from the above link): "If the identification must occur at compile time, then it is acceptable to use an empty interface."
RuthWa 12-Feb-24 18:50pm    
Perhaps I nitpicked to those rules a little bit and overrated their importance as extremely vital. Thanks. I'll reconsider my approach to using rules.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900