Matias Cicero
Matias Cicero

Reputation: 26281

How to translate this extension method to an interface method

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

Answers (1)

Sergey Kalinichenko
Sergey Kalinichenko

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

Related Questions