Yes, compiler has all the information to infer the type for TSomeReturnType
when you pass WindowVm
as TViewModel
. But the main obstacle for allowing reduced argument list for generic (.DialogWithResult<WindowVm>()
) is that it could conflict with overloaded method with the same name but just one generic type argument. For example if you have following methods in the class:
public IDialogWithResult<TViewModel, TSomeReturnType> DialogWithResult<TViewModel,TSomeReturnType>(object owner = null)
where TViewModel : DialogResultBase<TSomeReturnType>
public IDialogWithResult<TViewModel> DialogWithResult<TViewModel>(object owner = null)
where TViewModel : DialogResultBase<MyReturnType>
Which one should compiler call when you code .DialogWithResult<WindowVm>()
?
That's the reason why such simplified syntax will probably not be introduced in C#.
However you still have an option to make the calls as simple as .DialogWithResult<WindowVm>()
. I'm not a fan of this solution but if brevity of your Fluent Api calls is important, you could use it. The solution is based on reflection and run-time extraction of TSomeReturnType
type from passed TViewModel
type:
public class YourClass
{
public dynamic DialogWithResult<TViewModel>(object owner = null)
{
// Searching for DialogResultBase<TSomeReturnType> in bases classes of TViewModel
Type currType = typeof(TViewModel);
while (currType != null && currType != typeof(DialogResultBase<>))
{
if (currType.IsGenericType && currType.GetGenericTypeDefinition() == typeof(DialogResultBase<>))
{
break;
}
currType = currType.BaseType;
}
if (currType == null)
{
throw new InvalidOperationException($"{typeof(TViewModel)} does not derive from {typeof(DialogResultBase<>)}");
}
Type returnValueType = currType.GetGenericArguments()[0];
// Now we know TViewModel and TSomeReturnType and can call DialogWithResult<TViewModel, TSomeReturnType>() via reflection.
MethodInfo genericMethod = GetType().GetMethod(nameof(DialogWithResultGeneric));
if (genericMethod == null)
{
throw new InvalidOperationException($"Failed to find {nameof(DialogWithResultGeneric)} method");
}
MethodInfo methodForCall = genericMethod.MakeGenericMethod(typeof(TViewModel), returnValueType);
return methodForCall.Invoke(this, new [] { owner } );
}
public IDialogWithResult<TViewModel, TSomeReturnType> DialogWithResultGeneric<TViewModel, TSomeReturnType>(object owner = null)
where TViewModel : DialogResultBase<TSomeReturnType>
{
// ...
}
}
We declared new DialogWithResult<TViewModel>()
method with just one generic type argument of TViewModel
. Then we search for the base DialogResultBase<T>
class. If found we extract type of TSomeReturnType
with Type.GetGenericArguments()
call. And finally call original DialogWithResultGeneric<TViewModel, TSomeReturnType>
method via reflection. Note that I have renamed original method to DialogWithResultGeneric
so that GetMethod()
does not throw AmbiguousMatchException
.
Now in your program you could call it as:
.DialogWithResult<WindowVm>()
The downside is that nothing prevents you from calling it on wrong type (the one does not inherit from DialogResultBase<T>
):
.DialogWithResult<object>()
You won't get compilation error in this case. The problem will be identified only during run-time when exception will be thrown. You could fix this issue with a technique described in this answer. In short, you should declare non-generic DialogResultBase
and set it as the base for DialogResultBase<T>
:
public abstract class DialogResultBase
{
}
public class DialogResultBase<T> : DialogResultBase
{
// ...
}
Now you could add constraint on DialogWithResult<TViewModel>()
type parameter:
public dynamic DialogWithResult<TViewModel>(object owner = null)
where TViewModel : DialogResultBase
Now .DialogWithResult<object>()
will cause compilation error.
Again, I'm not a big fan of solution that I proposed. However you can't achieve what you're asking for with just C# capabilities.