0

I'm digging in to custom casts for C# classes. This StackOverflow question gave me a head start, but my class has Type arguments in it. An example is below:

Say I have this class hierarchy for my Entities:

public class Animal
{
}

public class Tiger : Animal
{
    public int NumberOfStripes { get; set; }
}

(where Tiger has some properties that Animal does not).

Now, the class I'm trying to perform a custom cast on is similar to the one below (I'm using ASP.NET MVC as a side note)

public class SomeViewModel<T> where T : Animal
{
    public T Animal { get; set; }
    ...
}

When creating the edit forms for this class hierarchy, I need forms specific to an Animal sub class, but the instantiation of the actual animal is done using a Type object. At some point, I need to cast SomeViewModel<Animal> to SomeViewModel<Tiger> so I can use a strongly typed Razor view.

An example controller:

namespace MvcProject.Controllers
{
    public class AnimalsController : Controller
    {
        public ActionResult Create(int id)
        {
            AnimalType t = DbContext.AnimalTypes.Find(id); // get AnimalType object from database
            AnimalViewModel<Animal> model = new AnimalViewModel<Animal>()
            {
                Animal = (Animal)t.CreateInstance() // Returns a Tiger object cast to an Animal
            };

            return View(model);
        }
    }
}

In my Razor view, I want to render a partial that is strongly typed to the Tiger class like so:

@Html.Partial("_TigerForm", Model)

And the contents of the _TigerForm Razor partial:

@model AnimalViewModel<Tiger>

<p>Number of Stripes: @Model.Animal.NumberOfStripes</p>

So couple of questions here:

  1. Can you do this sort of type cast (from AnimalViewModel<Animal> to AnimalViewModel<Tiger>)?
  2. What other options would be available that would not require type casting? I'd like to avoid a Dynamic view if possible.

Edit #1

Consider the following code:

List<Animal> a = new List<Animal>();
List<Tiger> b = (List<Tiger>)a;

Visual Studio still complains that it cannot cast the object. This seems to be the root of my problem.

Edit #2

This code does work though:

Animal a = new Tiger();
Tiger b = (Tiger)a;
Community
  • 1
  • 1
Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
  • About your edit, a list of animals is NOT a list of tigers... it may contain tigers, it may be made of ONLY tigers but there is no way of knowing at compile time... – Tallmaris Mar 21 '14 at 17:51

3 Answers3

1

If you used interfaces you can achieve this.

So your model classes would become:

public interface IAnimal
{
}

public Interface ITiger : IAnimal
{
    int NumberOfStripes { get; set; }
}

public class Animal : IAnimal
{
}

public class Tiger : Animal, ITiger
{
    public int NumberOfStripes { get; set; }
}

Now your view model would change to accept the interface:

public class SomeViewModel<T> where T : IAnimal
{
    public T Animal { get; set; }
    ...
}

Then your Razor view can use the interfaces and the casting will work because the model implements the required interfaces:

@model AnimalViewModel<IAnimal>

@Html.Partial("_TigerForm", Model)

And the contents of the _TigerForm Razor partial:

@model AnimalViewModel<ITiger>

<p>Number of Stripes: @Model.Animal.NumberOfStripes</p>

Hope that helps!

Richard Seal
  • 4,248
  • 14
  • 29
  • Interesting. I'll have to try using interfaces, though I'm not sure why casting interfaces would work when casting concrete classes does not, because in both cases the inheritance hierarchy is the same. Still worth a try. – Greg Burghardt Mar 21 '14 at 17:36
  • I am always pro interfaces, but you don't necessarily need them in this case. – TyCobb Mar 21 '14 at 17:36
  • Still no dice. Now I'm getting this error: The model item passed into the dictionary is of type 'AnimalViewModel`1[IAnimal]', but this dictionary requires a model item of type 'AnimalViewModel`1[ITiger]'. – Greg Burghardt Mar 21 '14 at 17:43
  • Edited my question with some more info. I'm starting to wonder if what I want to do just isn't possible. – Greg Burghardt Mar 21 '14 at 17:47
1

Your problem is that you are not creating the correct view model.

public ActionResult Create(int id)
{
    AnimalType t = DbContext.AnimalTypes.Find(id); // get AnimalType object from database
    AnimalViewModel<Animal> model = new AnimalViewModel<Animal>()
    {
       Animal = (Animal)t.CreateInstance() // Returns a Tiger object cast to an Animal
    };

    return View(model);
}

What you need to do is get a handle on the correct ViewModel type and create an instance of that with the correct generic. An Animal will always be an Animal no matter if you set it to a tiger or anything else unless you cast it to what it is supposed to be or attempt to call everything dynamically.

The code below will actually create you an AnimalViewModel<Tiger> instead of AnimalViewModel<Animal> which is causing your issues.

public class AnimalViewModel<T> where T : Animal
{
    public T Animal { get; set; }

    public AnimalViewModel(T animal) 
    {
         Animal = animal;
    }
}
public ActionResult Create(int id)
{
    AnimalType t = DbContext.AnimalTypes.Find(id); // get AnimalType object from database
    Animal animal = (Animal)t.CreateInstance();
    var animalViewModel =  
        Activator.CreateInstance(typeof(AnimalViewModel<>).MakeGenericType(animal.GetType()),
                                 animal)

    return View(animalViewModel);
}
TyCobb
  • 8,909
  • 1
  • 33
  • 53
  • I just ran across this StackOverflow question (http://stackoverflow.com/questions/4661211/c-sharp-instantiate-generic-list-from-reflected-type), which lead me in the same direction as your answer. I need to give this a try. – Greg Burghardt Mar 21 '14 at 18:49
  • I will also render a view expecting that type as well. You wouldn't know from my question, but the `AnimalType` object has a string property called `Code`, which could be used to render the view: `return View("AnimalForms/" + t.Code, animalViewModel)` – Greg Burghardt Mar 21 '14 at 20:05
0

If you want to use the same "base" view and then put different partials based on the type of Animal the only solution that pops into mind now is an if:

@if (Model is Tiger)
{
    @Html.Partial("_TigerForm", Model)
}

You should not need to cast Model, since this kind of binding in the View is done at runtime so it will not give you compile errors (maybe warnings, yes).

Of course, the moment you start adding a lot of this ifs, you should start thinking about creating an Html Helper that will render the right Partial based on the type.

Even better, you may be able to use some kind of convention and do this:

@Html.Partial("_" + Model.GetType().Name + "Form", Model)

Disclaimer: not tested so may be a bit more problematic than it seems. :)

Tallmaris
  • 7,605
  • 3
  • 28
  • 58