1

I've got a question about how the compiler infers types when using Generics and Extension Methods. The easiest way to ask my question is to show some code first...

I've got a ViewModelBase class that looks a bit like this (with everything that is not relevant taken out). Basically my NavigationService class calls the NavigatingTo method each time the application goes to the View that it is linked up with.

Inheriters of this class can call the Return delegate to pass data back to the caller.

public abstract class ViewModelBase<TContext, TReturn>
{
    public Action<TReturn> Return { get; set; }

    public abstract void NavigatingTo(TContext context);
}

Then I have a test ViewModel that inherits ViewModelBase:

public class TestViewModel : ViewModelBase<int, bool>
{
    public override void NavigatingTo(int context)
    {
        // do stuff here
    }
}

Next I have a generic NavigationCommand class that accepts a ViewModelBase like this:

public class NavigationCommand<TViewModel>
{
    public TViewModel ViewModel { get; set; }

    public NavigationCommand(TViewModel viewModel)
    {
        this.ViewModel = viewModel;
    }
}

And lastly I have an Extension Method for the NavigationCommand class to add a Navigate method to it. My aim here is that by stating that my Navigate method requires a ViewModelBase with TContext and TReturn, the compiler should be able to infer what types are actually to be used:

public static class NavigationExtensions
{
    // I actually pass in a INavigationService here too, but I have left that out to
    // make it easier to read...

    public static void Navigate<TViewModel, TContext, TReturn>(
        this NavigationCommand2<TViewModel> viewModel, 
        TContext context, 
        Action<TReturn> returnAction) 
        where TViewModel : ViewModelBase<TContext, TReturn>
    {
        // actual implementation omitted
    }
}

Ok, so now in my ApplicationController class I do the following:

var vm = new TestViewModel();
var cmd = new NavigationCommand2<TestViewModel>(vm);

int clientID = 1;

Action<bool> returnAction = success =>
{
    Console.WriteLine(success.ToString());
};

cmd.Navigate(clientID, returnAction);

It works, and if you try to pass in an incorrect type you get a compiler error when you build. However Intellisense does not prompt you for the correct types.

So my question: Is there any way to rewrite the extension method, or my NavigationCommand class or ViewModel, etc so that Intellisense actually prompts me to use the correct type?

Currently all Intellisense gives me is this:

(extension void) NavigateCommand<TestViewModel>.Navigate(TContext context, Action<TReturn> returnAction)

When what I want is this:

(extension void) NavigateCommand<TestViewModel>.Navigate(int context, Action<bool> returnAction)
Jon
  • 428,835
  • 81
  • 738
  • 806

1 Answers1

1

One of the ways to solve this issue is to propagate TContext and TReturn type parameters to the NavigationCommand, so its declaration will look like:

public class NavigationCommand<TViewModel, TContext, TReturn> where TViewModel:ViewModelBase<TContext, TReturn>

But it makes the command initialization more verbose (because the TestViewModel type in fact already includes the information about TContext and TReturn actual types):

var cmd = new NavigationCommand<TestViewModel, int, bool>(vm);

Actually, the implementation you have posted is already type-safe and will not allow you to pass the argument of incorrect type (let's say string instead of int). The only issue is Intellisense that is not able to infer the type correctly for some reason.

Andrew Khmylov
  • 732
  • 5
  • 18
  • Thanks Andrew. I really wanted to avoid having to define the types in the NavigationCommand explicitly, because as you say it is verbose, but also it's one more place you have to change things if the context or return type change. Any ideas as to why Intellisense does not pick this up? – Adam Valpied Sep 12 '11 at 13:10
  • I think, the following post describes the reason: [http://stackoverflow.com/questions/7171067/no-type-inference-with-generic-extension-method/7171527#7171527](http://stackoverflow.com/questions/7171067/no-type-inference-with-generic-extension-method/7171527#7171527) – Andrew Khmylov Sep 13 '11 at 05:58
  • Thanks again Andrew. I had actually read that post and blog before posting this question, but felt my situation was slightly different. In the blog, Eric seem to be discussing why the compiler cannot infer types in certain scenarios and so throws an compile-time error. In my case the compiler correctly identifies the type, and will not let you use a variable of a type that is incompatible with the types defined in the view model. The problem is that IntelliSense does not pick it up. Thanks for your time. I think I will just change my approach and leave it at that. – Adam Valpied Sep 13 '11 at 09:04