Reputation: 26281
Let's say I have the following extension method:
public static TContext Timeout<TContext>(this TContext context, TimeSpan timeout)
where TContext : IContext
{
// ...
}
This extension method allows me to achieve immutability and preserve the calling type:
ITransactionalContext c = // ...;
c = c.Timeout(TimeSpan.FromSeconds(5));
However, how can I achieve this with an interface?
For instance, this won't be the same:
public interface IContext
{
IContext Timeout(TimeSpan timeout);
}
Because my code example won't compile.
ITransactionalContext c = // ...;
c = c.Timeout(TimeSpan.FromSeconds(5)); // <-- An IContext is returned. Cannot assign to variable.
I can specify the context type to the interface as a generic argument:
public interface IContext
{
}
public interface IContext<TContext> : IContext
where TContext : IContext
{
TContext Timeout(TimeSpan timeout);
}
public interface ITransactionalContext : IContext<ITransactionalContext>
{
}
But that doesn't seem nice at all.
Also, what if ITransactionalContext
requires more arguments:
public interface ITransactionalContext<TTransaction, TEntity>
: IContext<ITransactionalContext<TTransaction, TEntity>>
{
}
That is some generics mess.
Is there a cleaner way to achieve what the extension method achieves?
Upvotes: 0
Views: 61
Reputation: 726479
Your solution with an extension method is ideal, because it relies on compiler's ability to infer TContext
. Its major shortcoming, however, is that it does not IContext
classes provide their own implementations of Timeout(TimeSpan)
method. Effectively, the control of the cloning process is taken out of IContext
implementations.
If you want to keep the control of cloning in IContext
implementations, make a combination of an interface method and an extension method, like this:
interface IContext {
... // Your methods
IContext CloneWithTimeout(TimeSpan timeout);
}
...
public static TContext Timeout<TContext>(this TContext context, TimeSpan timeout)
where TContext : IContext
{
return (TContext)context.CloneWithTimeout(timeout);
}
Your use case continues to compile, while the process of producing a new instance is controlled entirely by implementations of IContext
.
Upvotes: 1