0

I have the following design of objects and classes. As mentioned in the comments of the method Play(Animal a), I would like to be able to test that a is effectively of type Cat<Big> and cast a accordingly so that I could access the method MethodUniqueToCats().

I am able to get Big via a.GetType().GetGenericArguments()[0]. But, somehow I am failing to make the leap on how to go from Animal to Cat<Big>. I believe that it is possible because Visual Studio is able to determine this info at runtime (checked via debug + breakpoints inside the method Play(Animal a)).

interface Animal
{
}

class Cat<T> : Animal
{
    public void MethodUniqueToCats()
    {
    }
}

class Dog<T> : Animal
{

}

class Freetime
{
    private Animal my_animal;

    public void Play(Animal a)
    {
        my_animal = a;
        Type t = a.GetType().GetGenericArguments()[0];
        // I would like to test if the type of 'a' passed to this 
        // method is a Cat and subsequently cast it to a Cat of type 't'
        // so that I can access 'MethodUniqueToCats()'.

        // Line below does not work but wondering how to go about:
        // if (a.GetType().IsAssignableFrom(typeof(Cat<t>))
        // How to do the 'casting'
    }
}

class MyProgram
{
    public static void Main(string[] args)
    {
        Freetime f = new Freetime();
        Cat<Big> c = new Cat<Big>();
        f.Play(c);
    }
}

Thanks in advance.

  • If you need to do that, you're violating the [Liskov Substitution Principle](http://stackoverflow.com/questions/56860/what-is-the-liskov-substitution-principle) – DavidG Mar 17 '17 at 23:31
  • You can't do that, except if the object passed by that interface really _is_ the type you want to cast it to. You cannot cast it to a different type than the one it really _is_ (and it's base types). – Psi Mar 17 '17 at 23:34
  • You have no generic code in your question. Yes you are using generics but their is no generic code. Plus if you need to cast to the concrete type then do not send in an interface because then your code is not tied to the interface but to the actual type. – CodingYoshi Mar 17 '17 at 23:35
  • @Psi: I would like to test for the type as indicated by the wish in the comment section of the code and then cast. – Pieru Poika Mar 17 '17 at 23:46
  • @CodingYoshi: This is just skeleton of the code and is a mock. You can assume that Generics is utilized but not at those points – Pieru Poika Mar 17 '17 at 23:46
  • @DavidG: Can you share how this is violating the LSP? I am not familiar with it and read the link you gave but, i cannot see what is being done there to violate it. Generally, Animal types are sufficient but, occasionally, the method would be given a Cat and hence the flow in that method deviate just like if statements in any other code pieces redirects flow. Thanks in advance. – Pieru Poika Mar 17 '17 at 23:56
  • Basically, if you are passing around an `Animal` interface, then you should treat is as an animal. The moment you try to treat it as a `Cat` is when you violate the LSP. *Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.* – DavidG Mar 17 '17 at 23:59
  • @DavidG: If i hear you correctly, this would mean that per the design we would need another method like `PlayWithCat(Cat c)` and `PlayWithDog(Dog d)`, etc? – Pieru Poika Mar 18 '17 at 00:05
  • Actually more likely `Play` would be part of the `Animal` interface. – DavidG Mar 18 '17 at 00:08
  • @DavidG: Perhaps i explained it wrong: I don't mean a common method inherited across all the types as you suggest above. I am looking at the fact that `Cat` has a method unique to cat (e.g. `MethodUniqueToCats()` does not exist in `Dog` or any other animal implementing/deriving from `Animal`). This is why I was suggesting to have `PlayWithCat(Cat c)` so that there i could access that special method. I was then extending the same reasoning to other types like `Dog`. Wouldn't that be the design strategy if i do not want to violate LSP? – Pieru Poika Mar 18 '17 at 00:14
  • The point is that once you have an object being passed around as an `Animal`, that it should stay that way and no code dealing with it should ever know if that object is a `Cat` or a `Dog`. – DavidG Mar 18 '17 at 00:31
  • Listen to your code. If you have code that depends not only on the animal interface but the concrete type and needs MethodUniqueToCats then do the common part and afterwards do the unique part elsewhere. Your code should not be trying to cast the interface to a concrete type or else there is no point of passing the interface. – CodingYoshi Mar 18 '17 at 00:52
  • There are ways to achieve what you want but it is a bad design hence I am suggesting not to follow that path. – CodingYoshi Mar 18 '17 at 00:54
  • @davidg while I do agree with you that the OP should treat types of animal as animal, I disagree what the OP wants to do is breaking LSP. – CodingYoshi Mar 18 '17 at 00:57

2 Answers2

0

How about this?

 if (a.GetGenericTypeDefinition() == typeof(Cat<>))
    {
       a.GetMethod("MethodUniqueToCats").Invoke(null, null)
    }

I wouldn't do that personally unless you absolutely have to. I would avoid reflection here and i.e I would add interfaces ICat and IDogand try to cast to it in your method.

MistyK
  • 6,055
  • 2
  • 42
  • 76
  • Can you share why you wouldn't do it and why would it be desirable to avoid reflection? – Pieru Poika Mar 17 '17 at 23:49
  • @PieruPoika You can read this topic: http://softwareengineering.stackexchange.com/questions/143205/reflection-is-using-reflection-still-bad-or-slow-what-has-changed-with-ref – MistyK Mar 18 '17 at 00:06
  • @PieruPoika Then you must implement a strategy pattern – Dan Hunex Mar 20 '17 at 16:58
0

If you absolutely want to do it this way (and violate the Liskov Substitution Principle) then the simplest way would be to use an interface for Cat, something like this:

interface IAnimal
{
}

interface ICat
{
    void MethodUniqueToCats();
}

class Cat<T> : IAnimal, ICat
{
    public void MethodUniqueToCats()
    {
    }
}

And now you can test that your object is a cat like this:

IAnimal animal = new Cat<int>();

var cat = animal as ICat;
if (cat != null)
{
    cat.MethodUniqueToCats();
}

Or if you're lucky enough to be running C# v7 (i.e. Visual Studio 2017), you can do this:

if (animal is ICat cat)
{
    cat.MethodUniqueToCats();
}
Community
  • 1
  • 1
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • 1
    I see the point of the Liskov Substitution Principle (LSP) a bit more now after reading a few more articles. I think it should not be violated. So, i mark this explanation as an answer and highlight: avoid using it if possible since it violates LSP :) – Pieru Poika Mar 18 '17 at 00:51
  • That's correct answer but it would make sense to implement IAnimal from ICat interface not a Cat class. – MistyK Mar 19 '17 at 09:32