C
C#3w ago
Core

Static class or regular class with interface?

Hello, I've been building a QR code generator for a while. It's a simple method call QrCodeGenerator.Generate("data", options).
c#
var qrCode = QrCodeGenerator.Generate("data", new QrCodeOptions
{
MarkerLeft = new MarkerOptions
{
OuterStyle = markerStyle,
InnerStyle = markerStyle
},
MarkerRight = new MarkerOptions
{
OuterStyle = markerStyle,
InnerStyle = markerStyle
},
MarkerBottom = new MarkerOptions
{
OuterStyle = markerStyle,
InnerStyle = markerStyle
},
PatternStyle = PatternStyle.Circle,
Format = ImageFormat.Png,
});
c#
var qrCode = QrCodeGenerator.Generate("data", new QrCodeOptions
{
MarkerLeft = new MarkerOptions
{
OuterStyle = markerStyle,
InnerStyle = markerStyle
},
MarkerRight = new MarkerOptions
{
OuterStyle = markerStyle,
InnerStyle = markerStyle
},
MarkerBottom = new MarkerOptions
{
OuterStyle = markerStyle,
InnerStyle = markerStyle
},
PatternStyle = PatternStyle.Circle,
Format = ImageFormat.Png,
});
It is a static method/class, I was wondering if it would be better to supply the class trough an interface, making it available trough DI. Suppose it's going to be a nuget package, would it make a difference if it was supplied trough DI, instead of a static class?
12 Replies
FusedQyou
FusedQyou3w ago
It depends Like literally everything else A static class is easy to access, but generally you should only do this for more basic helper methods and things that don't depend on eachother A QR code generator sounds like it could use dependencies. At the very least logging. Also, it's easier to test the generator when it's a DI injected class Maybe one thing to also consider is if the generator persists a state. If it does it should probably not be static.
Jimmacle
Jimmacle3w ago
ergonomics-wise it might be more convenient to persist the configuration instead of having to pass it every time if you typically use the same configuration
Core
CoreOP3w ago
There is no state at all Right, this one makes a difference
Jimmacle
Jimmacle3w ago
you could have it both ways, a stored configuration but still allow passing one to override the stored one
Core
CoreOP3w ago
That is what I currently have:
c#
public static byte[] Generate(string data, QrCodeOptions? options = null)
{
options ??= new QrCodeOptions();
}
c#
public static byte[] Generate(string data, QrCodeOptions? options = null)
{
options ??= new QrCodeOptions();
}
I guess my initial design can stay afterall
Jimmacle
Jimmacle3w ago
if you have stored configuration then it shouldn't be static imo
Core
CoreOP3w ago
The class only contains values, like default colors
c#
ublic class QrCodeOptions
{
public int Size { get; set; } = 1000;
public int Margin { get; set; } = 64;
public SKColor ForegroundColor { get; set; } = SKColors.Black;
public SKColor BackgroundColor { get; set; } = SKColors.White;
}
c#
ublic class QrCodeOptions
{
public int Size { get; set; } = 1000;
public int Margin { get; set; } = 64;
public SKColor ForegroundColor { get; set; } = SKColors.Black;
public SKColor BackgroundColor { get; set; } = SKColors.White;
}
Jimmacle
Jimmacle3w ago
yeah that wouldn't be a static class then, as soon as there's mutable state i make them regular classes (including the one storing the options instance)
Core
CoreOP3w ago
I thought state is a class property which would make a class not thread safe But I will modify it accordingly Would this not be thread safe? Each call would create a new options instance
Jimmacle
Jimmacle3w ago
all my comments are related to persisting a configuration in the class as a property or field, yes
Rory
Rory2w ago
Some additional thoughts on this, just for more options for you: Static classes and methods aren't incompatible with DI or testing! You could define a delegate matching the function signature and register that, and then inject it as you would an interface.
public delegate QrCode GenerateQr(string data, QrCodeOptions options);

services.AddSingleton<GenerateQr>(QrCodeGenerator.Generate);

public class Consumer
{
private GenerateQr makeQr;
public Consumer(GenerateQr makeQr)
{
this.makeQr = makeQr;
}

public DoSomethingWith(string data)
{
this.makeQr(data, new QrCodeOptions()); //etc
}
}
public delegate QrCode GenerateQr(string data, QrCodeOptions options);

services.AddSingleton<GenerateQr>(QrCodeGenerator.Generate);

public class Consumer
{
private GenerateQr makeQr;
public Consumer(GenerateQr makeQr)
{
this.makeQr = makeQr;
}

public DoSomethingWith(string data)
{
this.makeQr(data, new QrCodeOptions()); //etc
}
}
You could even simplify the delegate and "hide" the config to standardise it across your app
public delegate QrCode GenerateQr(string data);

var myConfig = new QrCodeOptions { };

services.AddSingleton<GenerateQr>(data => QrCodeGenerator.Generate(data, myConfig);
public delegate QrCode GenerateQr(string data);

var myConfig = new QrCodeOptions { };

services.AddSingleton<GenerateQr>(data => QrCodeGenerator.Generate(data, myConfig);
But as others say, keep this stateless if you keep it static.
Anton
Anton2w ago
you just read it ofc it will be thread safe

Did you find this page helpful?