Can I help the C# compiler to infer this type?
up vote
5
down vote
favorite
I'm having an issue with type inference and the C# compiler. Having read this question and this question I think I understand why it isn't working: what I would like to know is if there is any way I can work-around the problem in order to get my preferred calling semantics.
Here is some code that illustrates my problem (sorry for the length, this is the shortest I could reduce it to):
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace StackOverflow
{
interface IQuery<TResult> { }
class MeaningOfLifeQuery : IQuery<int> { }
interface IQueryHandler<TQuery, TResult> where TQuery : class, IQuery<TResult>
{
Task<TResult> ExecuteAsync(TQuery query);
}
class MeaningOfLifeQueryHandler : IQueryHandler<MeaningOfLifeQuery, int>
{
public Task<int> ExecuteAsync(MeaningOfLifeQuery query)
{
return Task.FromResult(42);
}
}
interface IRepository
{
Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query);
Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>;
}
class Repository : IRepository
{
public Repository(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
readonly IServiceProvider _serviceProvider;
public async Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query)
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync((dynamic) query);
}
public async Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(typeof(TQuery), typeof(TResult));
var handler = (IQueryHandler<TQuery, TResult>) _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync(query);
}
}
class Program
{
static void Main(string args)
{
var services = new ServiceCollection();
services.AddTransient<IQueryHandler<MeaningOfLifeQuery, int>, MeaningOfLifeQueryHandler>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
var repository = new Repository(serviceProvider);
var query = new MeaningOfLifeQuery();
int result = repository.ExecuteQueryDynamicallyAsync(query).Result;
result = repository.ExecuteQueryStaticallyAsync<MeaningOfLifeQuery, int>(query).Result;
// result = repository.ExecuteQueryStaticallyAsync(query).Result;
// Doesn't work: type arguments cannot be inferred
}
}
}
Basically I want to get the repository to execute queries simply by calling an ExecuteAsync
method and passing the query. To make this work I have to use dynamic
to resolve the query handler type at runtime, as per ExecuteQueryDynamicallyAsync
. Alternatively, I can specify both the query type and result type as type parameters when calling the method (as per ExecuteQueryStaticallyAsync
) but this is obviously quite a wordy call, particularly when the query and/or return types have long names.
I can get the compiler to infer both types using a method signature like this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored)
where TQuery : class, IQuery<TResult>
which allows me to do something like this:
var query = new MeaningOfLifeQuery();
...
... ExecuteQueryAsync(query, default(int)) ...
but then I have to pass a dummy value (that gets ignored) to the method with each call. Changing the signature to this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored = default(TResult))
where TQuery : class, IQuery<TResult>
to try to avoid passing in the value when calling doesn't work, as the compiler can no longer infer the result type (ie. back to square one).
I'm at a bit of a loss as to how I can solve this problem, or even if it is solvable. My only options seem to be to stick with the simple calling semantics (but have to rely upon dynamic
) or to change my architecture in some way. Is there any way I can get the calling semantics I want?
c# type-inference
add a comment |
up vote
5
down vote
favorite
I'm having an issue with type inference and the C# compiler. Having read this question and this question I think I understand why it isn't working: what I would like to know is if there is any way I can work-around the problem in order to get my preferred calling semantics.
Here is some code that illustrates my problem (sorry for the length, this is the shortest I could reduce it to):
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace StackOverflow
{
interface IQuery<TResult> { }
class MeaningOfLifeQuery : IQuery<int> { }
interface IQueryHandler<TQuery, TResult> where TQuery : class, IQuery<TResult>
{
Task<TResult> ExecuteAsync(TQuery query);
}
class MeaningOfLifeQueryHandler : IQueryHandler<MeaningOfLifeQuery, int>
{
public Task<int> ExecuteAsync(MeaningOfLifeQuery query)
{
return Task.FromResult(42);
}
}
interface IRepository
{
Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query);
Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>;
}
class Repository : IRepository
{
public Repository(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
readonly IServiceProvider _serviceProvider;
public async Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query)
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync((dynamic) query);
}
public async Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(typeof(TQuery), typeof(TResult));
var handler = (IQueryHandler<TQuery, TResult>) _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync(query);
}
}
class Program
{
static void Main(string args)
{
var services = new ServiceCollection();
services.AddTransient<IQueryHandler<MeaningOfLifeQuery, int>, MeaningOfLifeQueryHandler>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
var repository = new Repository(serviceProvider);
var query = new MeaningOfLifeQuery();
int result = repository.ExecuteQueryDynamicallyAsync(query).Result;
result = repository.ExecuteQueryStaticallyAsync<MeaningOfLifeQuery, int>(query).Result;
// result = repository.ExecuteQueryStaticallyAsync(query).Result;
// Doesn't work: type arguments cannot be inferred
}
}
}
Basically I want to get the repository to execute queries simply by calling an ExecuteAsync
method and passing the query. To make this work I have to use dynamic
to resolve the query handler type at runtime, as per ExecuteQueryDynamicallyAsync
. Alternatively, I can specify both the query type and result type as type parameters when calling the method (as per ExecuteQueryStaticallyAsync
) but this is obviously quite a wordy call, particularly when the query and/or return types have long names.
I can get the compiler to infer both types using a method signature like this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored)
where TQuery : class, IQuery<TResult>
which allows me to do something like this:
var query = new MeaningOfLifeQuery();
...
... ExecuteQueryAsync(query, default(int)) ...
but then I have to pass a dummy value (that gets ignored) to the method with each call. Changing the signature to this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored = default(TResult))
where TQuery : class, IQuery<TResult>
to try to avoid passing in the value when calling doesn't work, as the compiler can no longer infer the result type (ie. back to square one).
I'm at a bit of a loss as to how I can solve this problem, or even if it is solvable. My only options seem to be to stick with the simple calling semantics (but have to rely upon dynamic
) or to change my architecture in some way. Is there any way I can get the calling semantics I want?
c# type-inference
1
The compiler must be able to infer the type parameters from the method parameters. One parameter, two types to infer. It has no shot atint
. Nope, the return type plays no role. A hacky way is to add an extra parameter to represent the default return value. That is however easy to push too far.
– Hans Passant
Nov 19 at 12:24
Is there any reason you can't removeTQuery
and just replace withIQuery<TResult>
everywhere? What does having a separateTQuery
gain you?
– NetMage
Nov 19 at 21:28
Unfortunately constraints are not part of the signature and won't help you with type inference, and sinceTResult
is part of your return value, you can't infer in pieces, that I can see.
– NetMage
Nov 19 at 21:52
Also, the LDM decided type inference from constraints is a breaking change and dropped it. Sometimes it feels like we need C#+ to fix C#.
– NetMage
Nov 19 at 22:06
@NetMage Two main reasons. (1) TheExecuteAsync
method in each query handler would no longer be strongly typed, requiring a cast. (2) The dependency injection would no longer work: I need to ask the service provider to "give me a handler for this type of query", but I'd have no way of specifying the type of the query.
– Steven Rands
Nov 20 at 9:14
add a comment |
up vote
5
down vote
favorite
up vote
5
down vote
favorite
I'm having an issue with type inference and the C# compiler. Having read this question and this question I think I understand why it isn't working: what I would like to know is if there is any way I can work-around the problem in order to get my preferred calling semantics.
Here is some code that illustrates my problem (sorry for the length, this is the shortest I could reduce it to):
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace StackOverflow
{
interface IQuery<TResult> { }
class MeaningOfLifeQuery : IQuery<int> { }
interface IQueryHandler<TQuery, TResult> where TQuery : class, IQuery<TResult>
{
Task<TResult> ExecuteAsync(TQuery query);
}
class MeaningOfLifeQueryHandler : IQueryHandler<MeaningOfLifeQuery, int>
{
public Task<int> ExecuteAsync(MeaningOfLifeQuery query)
{
return Task.FromResult(42);
}
}
interface IRepository
{
Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query);
Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>;
}
class Repository : IRepository
{
public Repository(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
readonly IServiceProvider _serviceProvider;
public async Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query)
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync((dynamic) query);
}
public async Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(typeof(TQuery), typeof(TResult));
var handler = (IQueryHandler<TQuery, TResult>) _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync(query);
}
}
class Program
{
static void Main(string args)
{
var services = new ServiceCollection();
services.AddTransient<IQueryHandler<MeaningOfLifeQuery, int>, MeaningOfLifeQueryHandler>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
var repository = new Repository(serviceProvider);
var query = new MeaningOfLifeQuery();
int result = repository.ExecuteQueryDynamicallyAsync(query).Result;
result = repository.ExecuteQueryStaticallyAsync<MeaningOfLifeQuery, int>(query).Result;
// result = repository.ExecuteQueryStaticallyAsync(query).Result;
// Doesn't work: type arguments cannot be inferred
}
}
}
Basically I want to get the repository to execute queries simply by calling an ExecuteAsync
method and passing the query. To make this work I have to use dynamic
to resolve the query handler type at runtime, as per ExecuteQueryDynamicallyAsync
. Alternatively, I can specify both the query type and result type as type parameters when calling the method (as per ExecuteQueryStaticallyAsync
) but this is obviously quite a wordy call, particularly when the query and/or return types have long names.
I can get the compiler to infer both types using a method signature like this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored)
where TQuery : class, IQuery<TResult>
which allows me to do something like this:
var query = new MeaningOfLifeQuery();
...
... ExecuteQueryAsync(query, default(int)) ...
but then I have to pass a dummy value (that gets ignored) to the method with each call. Changing the signature to this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored = default(TResult))
where TQuery : class, IQuery<TResult>
to try to avoid passing in the value when calling doesn't work, as the compiler can no longer infer the result type (ie. back to square one).
I'm at a bit of a loss as to how I can solve this problem, or even if it is solvable. My only options seem to be to stick with the simple calling semantics (but have to rely upon dynamic
) or to change my architecture in some way. Is there any way I can get the calling semantics I want?
c# type-inference
I'm having an issue with type inference and the C# compiler. Having read this question and this question I think I understand why it isn't working: what I would like to know is if there is any way I can work-around the problem in order to get my preferred calling semantics.
Here is some code that illustrates my problem (sorry for the length, this is the shortest I could reduce it to):
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace StackOverflow
{
interface IQuery<TResult> { }
class MeaningOfLifeQuery : IQuery<int> { }
interface IQueryHandler<TQuery, TResult> where TQuery : class, IQuery<TResult>
{
Task<TResult> ExecuteAsync(TQuery query);
}
class MeaningOfLifeQueryHandler : IQueryHandler<MeaningOfLifeQuery, int>
{
public Task<int> ExecuteAsync(MeaningOfLifeQuery query)
{
return Task.FromResult(42);
}
}
interface IRepository
{
Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query);
Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>;
}
class Repository : IRepository
{
public Repository(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
readonly IServiceProvider _serviceProvider;
public async Task<TResult> ExecuteQueryDynamicallyAsync<TResult>(IQuery<TResult> query)
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync((dynamic) query);
}
public async Task<TResult> ExecuteQueryStaticallyAsync<TQuery, TResult>(TQuery query)
where TQuery : class, IQuery<TResult>
{
Type handlerType = typeof(IQueryHandler<,>).MakeGenericType(typeof(TQuery), typeof(TResult));
var handler = (IQueryHandler<TQuery, TResult>) _serviceProvider.GetRequiredService(handlerType);
return await handler.ExecuteAsync(query);
}
}
class Program
{
static void Main(string args)
{
var services = new ServiceCollection();
services.AddTransient<IQueryHandler<MeaningOfLifeQuery, int>, MeaningOfLifeQueryHandler>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
var repository = new Repository(serviceProvider);
var query = new MeaningOfLifeQuery();
int result = repository.ExecuteQueryDynamicallyAsync(query).Result;
result = repository.ExecuteQueryStaticallyAsync<MeaningOfLifeQuery, int>(query).Result;
// result = repository.ExecuteQueryStaticallyAsync(query).Result;
// Doesn't work: type arguments cannot be inferred
}
}
}
Basically I want to get the repository to execute queries simply by calling an ExecuteAsync
method and passing the query. To make this work I have to use dynamic
to resolve the query handler type at runtime, as per ExecuteQueryDynamicallyAsync
. Alternatively, I can specify both the query type and result type as type parameters when calling the method (as per ExecuteQueryStaticallyAsync
) but this is obviously quite a wordy call, particularly when the query and/or return types have long names.
I can get the compiler to infer both types using a method signature like this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored)
where TQuery : class, IQuery<TResult>
which allows me to do something like this:
var query = new MeaningOfLifeQuery();
...
... ExecuteQueryAsync(query, default(int)) ...
but then I have to pass a dummy value (that gets ignored) to the method with each call. Changing the signature to this:
Task<TResult> ExecuteQueryAsync<TQuery, TResult>(TQuery query, TResult ignored = default(TResult))
where TQuery : class, IQuery<TResult>
to try to avoid passing in the value when calling doesn't work, as the compiler can no longer infer the result type (ie. back to square one).
I'm at a bit of a loss as to how I can solve this problem, or even if it is solvable. My only options seem to be to stick with the simple calling semantics (but have to rely upon dynamic
) or to change my architecture in some way. Is there any way I can get the calling semantics I want?
c# type-inference
c# type-inference
asked Nov 19 at 12:11
Steven Rands
3,1551941
3,1551941
1
The compiler must be able to infer the type parameters from the method parameters. One parameter, two types to infer. It has no shot atint
. Nope, the return type plays no role. A hacky way is to add an extra parameter to represent the default return value. That is however easy to push too far.
– Hans Passant
Nov 19 at 12:24
Is there any reason you can't removeTQuery
and just replace withIQuery<TResult>
everywhere? What does having a separateTQuery
gain you?
– NetMage
Nov 19 at 21:28
Unfortunately constraints are not part of the signature and won't help you with type inference, and sinceTResult
is part of your return value, you can't infer in pieces, that I can see.
– NetMage
Nov 19 at 21:52
Also, the LDM decided type inference from constraints is a breaking change and dropped it. Sometimes it feels like we need C#+ to fix C#.
– NetMage
Nov 19 at 22:06
@NetMage Two main reasons. (1) TheExecuteAsync
method in each query handler would no longer be strongly typed, requiring a cast. (2) The dependency injection would no longer work: I need to ask the service provider to "give me a handler for this type of query", but I'd have no way of specifying the type of the query.
– Steven Rands
Nov 20 at 9:14
add a comment |
1
The compiler must be able to infer the type parameters from the method parameters. One parameter, two types to infer. It has no shot atint
. Nope, the return type plays no role. A hacky way is to add an extra parameter to represent the default return value. That is however easy to push too far.
– Hans Passant
Nov 19 at 12:24
Is there any reason you can't removeTQuery
and just replace withIQuery<TResult>
everywhere? What does having a separateTQuery
gain you?
– NetMage
Nov 19 at 21:28
Unfortunately constraints are not part of the signature and won't help you with type inference, and sinceTResult
is part of your return value, you can't infer in pieces, that I can see.
– NetMage
Nov 19 at 21:52
Also, the LDM decided type inference from constraints is a breaking change and dropped it. Sometimes it feels like we need C#+ to fix C#.
– NetMage
Nov 19 at 22:06
@NetMage Two main reasons. (1) TheExecuteAsync
method in each query handler would no longer be strongly typed, requiring a cast. (2) The dependency injection would no longer work: I need to ask the service provider to "give me a handler for this type of query", but I'd have no way of specifying the type of the query.
– Steven Rands
Nov 20 at 9:14
1
1
The compiler must be able to infer the type parameters from the method parameters. One parameter, two types to infer. It has no shot at
int
. Nope, the return type plays no role. A hacky way is to add an extra parameter to represent the default return value. That is however easy to push too far.– Hans Passant
Nov 19 at 12:24
The compiler must be able to infer the type parameters from the method parameters. One parameter, two types to infer. It has no shot at
int
. Nope, the return type plays no role. A hacky way is to add an extra parameter to represent the default return value. That is however easy to push too far.– Hans Passant
Nov 19 at 12:24
Is there any reason you can't remove
TQuery
and just replace with IQuery<TResult>
everywhere? What does having a separate TQuery
gain you?– NetMage
Nov 19 at 21:28
Is there any reason you can't remove
TQuery
and just replace with IQuery<TResult>
everywhere? What does having a separate TQuery
gain you?– NetMage
Nov 19 at 21:28
Unfortunately constraints are not part of the signature and won't help you with type inference, and since
TResult
is part of your return value, you can't infer in pieces, that I can see.– NetMage
Nov 19 at 21:52
Unfortunately constraints are not part of the signature and won't help you with type inference, and since
TResult
is part of your return value, you can't infer in pieces, that I can see.– NetMage
Nov 19 at 21:52
Also, the LDM decided type inference from constraints is a breaking change and dropped it. Sometimes it feels like we need C#+ to fix C#.
– NetMage
Nov 19 at 22:06
Also, the LDM decided type inference from constraints is a breaking change and dropped it. Sometimes it feels like we need C#+ to fix C#.
– NetMage
Nov 19 at 22:06
@NetMage Two main reasons. (1) The
ExecuteAsync
method in each query handler would no longer be strongly typed, requiring a cast. (2) The dependency injection would no longer work: I need to ask the service provider to "give me a handler for this type of query", but I'd have no way of specifying the type of the query.– Steven Rands
Nov 20 at 9:14
@NetMage Two main reasons. (1) The
ExecuteAsync
method in each query handler would no longer be strongly typed, requiring a cast. (2) The dependency injection would no longer work: I need to ask the service provider to "give me a handler for this type of query", but I'd have no way of specifying the type of the query.– Steven Rands
Nov 20 at 9:14
add a comment |
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53374391%2fcan-i-help-the-c-sharp-compiler-to-infer-this-type%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
The compiler must be able to infer the type parameters from the method parameters. One parameter, two types to infer. It has no shot at
int
. Nope, the return type plays no role. A hacky way is to add an extra parameter to represent the default return value. That is however easy to push too far.– Hans Passant
Nov 19 at 12:24
Is there any reason you can't remove
TQuery
and just replace withIQuery<TResult>
everywhere? What does having a separateTQuery
gain you?– NetMage
Nov 19 at 21:28
Unfortunately constraints are not part of the signature and won't help you with type inference, and since
TResult
is part of your return value, you can't infer in pieces, that I can see.– NetMage
Nov 19 at 21:52
Also, the LDM decided type inference from constraints is a breaking change and dropped it. Sometimes it feels like we need C#+ to fix C#.
– NetMage
Nov 19 at 22:06
@NetMage Two main reasons. (1) The
ExecuteAsync
method in each query handler would no longer be strongly typed, requiring a cast. (2) The dependency injection would no longer work: I need to ask the service provider to "give me a handler for this type of query", but I'd have no way of specifying the type of the query.– Steven Rands
Nov 20 at 9:14