Reputation: 29
I have an interface, IFoo
, that implements one method, Bar
. IFoo
accepts a generic argument T
. This generic argument defines the type of the parameter accepted by Bar
.
public interface IFoo<T>
{
public void Bar(T value);
}
Pretty basic. But, sometimes, it is not necessary for Bar
to accept any arguments. To implement this, I have created a second interface, also called IFoo
, that does not accept any generic arguments.
public interface IFoo
{
public void Bar();
}
This works, but it adds overhead anytime I might want to update IFoo
, given that there are technically two different IFoo
s. If I want to add a second method to the interface(s) I now have to do it twice. This also impacts any classes that implement IFoo
if I have a class Baz
that implements IFoo
and I want it to implement both versions of IFoo
, I have to define one version of Baz
that passes a generic to IFoo
and one that does not. For instance:
public class Baz<T> : IFoo<T>
{
private IFoo<T> _command;
public void Bar(T value) {
_command.Bar(value);
}
}
public class Baz : IFoo
{
private IFoo _command;
public void Bar() {
_command.Bar();
}
}
Both classes, Baz
are fairly similar. They both pass a function call to another IFoo
object. But since one takes an argument, and the other does not, they have to be two different classes, despite the fact that their functionality is nearly identical. Whatever changes I make to one I am guaranteed to make to the other. It would be ideal to have one class Baz
that can be instantiated with or without a generic depending on what type of argument it is meant to take and whether or not it should even take an argument at all.
// This code works with the two versions of Baz defined above.
// The goal would be to get this code to work with only one Baz class.
var intBaz = new Baz<int>();
var stringBaz = new Baz<string>();
var emptyBaz = new Baz();
intBaz.Bar(100);
stringBaz.Bar("It would be really nice if this is possible, thank you");
emptyBaz.Bar();
Is there anyway, within C#, to define an interface IFoo
that determines not only what type of argument its method Bar
accepts, but also whether or not Bar
accepts any argument at all? Thanks!
So far I have been able to work around this problem by implementing two, nearly identical, interfaces IFoo
and IFoo<T>
. This "works" to an extent but makes classes that implements IFoo
and IFoo<T>
difficult to maintain, as any class that implements one requires a second class that implements the other. Changes to one class must be reflected in changes to the other. At best, this adds time to development. At worst, this introduces vulnerabilities for the code to become out of sync.
Upvotes: 2
Views: 82
Reputation: 271420
I would define a Unit
type - a type that only has one valid value.
struct Unit {}
And use that on the generic interface whenever you want to say "I don't want arguments". This way, you can get rid of the non-generic interface.
Of course, you still have to pass new()
, but since that is the only value for this type, you are not actually giving any information, or doing any thinking.
var emptyBaz = new Baz<Unit>();
emptyBaz.Bar(new());
An alternative formulation is a class that cannot be instantiated:
var emptyBaz = new Baz<Unit>();
emptyBaz.Bar(null);
sealed class Unit {
private Unit() {}
}
The only value for this type is null
, so this might require you to do more null checking in your implementations.
Similar problems exist in the standard library, like (Value)Task<T>
and (Value)Task
etc. I don't think an "elegant" solution exists yet. If it did, the standard library would have used it.
Upvotes: 2
Reputation: 100547
You can use default implementation to provide the second method, which will give the implementing class an option to have a different implementation for both if needed:
public interface IFoo<T>
{
public void Bar(T value);
public void Bar() {Bar(default(T)!);}
}
Or actually provide default parameter to the interface:
public interface IFoo<T>
{
public void Bar(T value = default(T)!);
}
There is really no wayto implement a single class that is and isn't generic at the same time - you have to have two classes for that and two interfaces. You still can use default implementation in the generic interface to avoid code duplication.
Upvotes: 1