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:

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