Using an interface implemented by a struct to not depend on concretions but to depend on abstractions (SOLID)

60 views Asked by At

I am trying to follow the SOLID principles in my new C# 12/.NET 8 project.

I will start by providing an example:

Given two separate assemblies AssemblyA and AssemblyB where AssemblyA defines an interface it needs some implementation for and AssemblyB provides said implementation.

// In AssemblyA
public interface IFoo{
    public int SomeVariable { get; init; }
}

//In AssemblyB
public readonly struct Foo : IFoo
{
    private readonly int _SomeVariable;
    public int SomeVariable { get => _SomeVariable; init => _SomeVariable = value; }
}

//And then somewhere in AssemblyB is the function
public static IFoo GetFoo(){
    return new Foo{SomeVariable = 1};
}

Relevant principles in this example are "Interface Segregation Principle" and "Dependency Inversion Principle". With DIP we aim to make AssemblyA depend on an abstraction to leave the flexibility of providing the concretion to the AssemblyB. This makes it so that AssemblyB can decide what concretion to provide in what circumstances and it also aids with testability. With ISP we want our interfaces to be as small as needed so AssemblyA does not have to deal with all the complexity of Foo(assuming Foo is way more complex then shown in the example).

So the above example would obviously work but will result in boxing and thus reducing potential benefits of using a struct(allocation avoidance/stack allocation).

Another way of implementing GetFoo() would be to use generics with type constraints:

public static TFoo GetFoo<TFoo>() where TFoo : struct, IFoo{
    return new TFoo{SomeVariable = 1};
}

Which in turn forces AssemblyA to know Foo if it wants to avoid boxing. This then does not satisfy DIP and if Foo is more complex ISP is not satisfied as well.

Furthermore if we use the generic version and AssemblyA needs to know which type it gets returned at the time of calling there is no benefit for the generic implementation so one could simply return Foo directly.

From above is my observation correct that as soon as I start to use structs DIP and ISP are difficult or even impossible to satisfy? Unless I accept boxing but then I might as well use classes instead. What is the general view about this? Is there something I am missing that would allow DIP and ISP with structs while not boxing value types?

Note that this is pure conceptual and nothing here is based on measurements. I am neither at a place where I absolutely need to use structs, nor do I know if using struct would actually significantly benefit in my case. Treat this question as being pure conceptual.

0

There are 0 answers