Reputation: 32914
Given an interface like this:
public interface Transformer<TSource, TResult> {
TResult Transform(TSource original);
}
I want to provide a simple "no-op" implementation of that interface for cases where no transformation is needed, which would just return the original object itself. In that case, TSource
and TResult
would be the same.
Being an old Java dude who's still learning C#, my first though was just this:
public class NoopTransformer : Transformer<object, object> {
public override object Transform(object original) {
return original;
}
}
That compiles but I can't practically use it anywhere that just expects a Transformer
. If I try, I get compile errors that say NoopTransformer
can't be used as a Transformer<TSource, TResult>
What I long for is Java's wildcards or willingness to treat Object
as an acceptable type parameter for any object. Is there really no equivalent in C#?
Then I thought this would work:
public class NoopTransformer<TSource> : Transformer<TSource, TSource> {
public override TSource Transform(TSource original) {
return entity;
}
}
That doesn't work in any place that expects a Transformer<TSource, TResult>
, like this:
public class Controller<TEntity, TResult> {
private Transformer<TEntity, TResult> transformer;
public Controller() : this(new NoopTransformer<TEntity>()) // error on this line
{ }
public Controller(Transformer<TEntity, TResult> transformer) {
this.transformer = transformer;
}
}
That fails to compile, saying,
NoopTransformer<TEntity>
to Transformer<TSource, TResult>
NoopTransformer<TEntity>
is not assignable to parameter type Transformer<TEntity, TResult>
(even though it seems obvious that my impl satisfies that contract - the fact that TSource
and TResult
are the same shouldn't matter).
Finally, grasping at straws, I tried this:
public class NoopTransformer<TSource, TResult> : Transformer<TSource, TResult>
where TResult : TSource
{
public override TResult Transform(TSource original) {
return (TResult)entity;
}
}
But of course that doesn't work because where I want to use this, there is no constraint that says TResult
must extend TSource
.
The context is that I have another class that has the same TSource
and TResult
type parameters, and can accept a Transformer
in its constructor. But the transformer is optional in that class; I want to use NoopTransformer
in cases where the client doesn't specify a more specific Transformer
.
In Java this is trivial, in fact with a couple of different ways to solve it (using type wildcards, extends
, etc). I realize that Java is more lenient (due to its use of type erasure), and so is inherently more flexible since there are no type parameters at runtime. But it seems that the C# compiler is being unnecessarily strict (and maybe a little dumb).
How can I achieve my goal of having a useful default for the interface?
Upvotes: 1
Views: 2649
Reputation: 144136
The compiler is correct to reject
public Controller() : this(new NoopTransformer<TEntity>())
since a Transformer<TEntity, TEntity>
is not compatible with Transformer<TEntity, TResult>
since TEntity
and TResult
are not guaranteed to be the same e.g.
var c = new Controller<string, int>();
You can maintain static safety by creating a factory method for constructing a Controller
instance where TEntity
and TResult
are the same:
public static class Controller
{
public static Controller<TEntity, TEntity> Create<TEntity>()
{
return new Controller<TEntity, TEntity>(new IdentityTransformer<TEntity>());
}
}
if for some reason you cannot adopt this approach you will have to rely on casting at runtime:
public class CastingTransformer<TSource, TResult> : Transformer<TSource, TResult>
{
public TResult Transform(TSource original)
{
return (TResult)(object)original;
}
}
public Controller() : this(new CastingTransformer<TEntity, TResult>()) { }
Upvotes: 2
Reputation: 1264
Is this what you had in mind?
public interface Transformer<TSource, TResult>
{
TResult Transform(TSource original);
}
public class NoopTransformer<TSource, TResult> : Transformer<TSource, TResult> where TResult : class
{
public TResult Transform(TSource original)
{
return original as TResult;
}
}
public class Controller<TEntity, TResult> where TResult : class
{
private Transformer<TEntity, TResult> transformer;
public Controller() : this(new NoopTransformer<TEntity, TResult>())
{ }
public Controller(Transformer<TEntity, TResult> transformer)
{
this.transformer = transformer;
}
}
Upvotes: 0