org.sonar.plugins.csharp.S4023.html Maven / Gradle / Ivy
Empty interfaces should be avoided as they do not provide any functional requirements for implementing classes.
Why is this an issue?
Empty interfaces are either useless or used as a marker. Custom attributes are a better alternative to marker
interfaces. See the How to fix it section for more information.
Exceptions
This rule doesn’t raise in any of the following cases:
Aggregation of multiple interfaces
public interface IAggregate: IComparable, IFormattable { } // Compliant: Aggregates two interfaces
Generic specialization
An empty interface with a single base interface is compliant as long as the resulting interface binds a generic parameter or constrains it.
// Compliant: Bound to a concrete type
public interface IStringEquatable: IEquatable<string> { }
// Compliant: Specialized by type parameter constraint
public interface ICreateableEquatable<T>: IEquatable<T> where T: new() { }
Custom attribute
An empty interface is compliant if a custom attribute is applied to the interface.
[Obsolete]
public interface ISorted { } // Compliant: An attribute is applied to the interface declaration
How to fix it
Do any of the following to fix the issue:
- Add members to the interface
- Remove the useless interface
- Replace the usage as a marker interface with custom attributes
Code examples
Noncompliant code example
The empty interface does not add any functionality.
public interface IFoo // Noncompliant
{
}
Compliant solution
Add members to the interface to be compliant.
public interface IFoo
{
void Bar();
}
Noncompliant code example
A typical use case for marker interfaces is doing type inspection via reflection as shown below.
The IIncludeFields
marker interface is used to configure the JSON serialization of an object.
// An example marker interface
public interface IIncludeFields { }
public class OptInToIncludeFields: IIncludeFields { }
Serialize(new OptInToIncludeFields());
void Serialize<T>(T o)
{
// Use reflection to check if the interface is applied to the type
var includeFields = o.GetType()
.GetInterfaces().Any(i => i == typeof(IIncludeFields));
var options = new JsonSerializerOptions()
{
// Take decisions based on the presence of the marker
IncludeFields = includeFields,
};
}
The same example can be rewritten using custom attributes. This approach is preferable because it is more fine-grained, allows parameterization,
and is more flexible in type hierarchies.
// A custom attribute used as a marker
[AttributeUsage(AttributeTargets.Class)]
public sealed class IncludeFieldsAttribute: Attribute { }
[IncludeFields]
public class OptInToIncludeFields { }
Serialize(new OptInToIncludeFields());
void Serialize<T>(T o)
{
// Use reflection to check if the attribute is applied to the type
var includeFields = o.GetType()
.GetCustomAttributes(typeof(IncludeFieldsAttribute), inherit: true).Any();
var options = new JsonSerializerOptions()
{
// Take decisions based on the presence of the marker
IncludeFields = includeFields,
};
}
Resources
Documentation
- Microsoft Learn - Interfaces - define behavior for
multiple types
- Wikipedia - Marker interface pattern
- Microsoft Learn - Writing custom
attributes