Search Unity

C# explicit casting to derived classes

Discussion in 'Scripting' started by probbins, Nov 12, 2013.

  1. probbins

    probbins

    Joined:
    Oct 19, 2010
    Posts:
    216
    It is common in games to have a lot of classes with both shared and specific implementations, sometimes you want to run specific logic if it is of a specific type.

    As an example you have a input script which detects when you click on a game object, if the game object has a component which is of a certain type then you pass the component to other classes which require it such as the UI, the UI now might need to do run certain logic depending on what the derived class is.

    Generic code example:

    Code (csharp):
    1.  
    2. public interface IEntity {
    3.  
    4.     // related properties and methods
    5.  
    6. }
    7.  
    8. public class Human : IEntity {
    9.  
    10.     // related properties and methods
    11.  
    12. }
    13.  
    14. public class Monster : IEntity {
    15.  
    16.     // related properties and methods
    17.  
    18. }
    19.  
    Code (csharp):
    1.  
    2. public void SomeMethod(IEntity entity) {
    3.  
    4.     // Alternative to using IS then cast.
    5.     Monster monster = entity as Monster;
    6.     if(monster != null) {
    7.         monster.DoMonsterSpecificMethod();
    8.     }
    9.  
    10. }
    11.  
    The general view on stackoverflow is that in C# it is consider it bad design, but that might not necessarily be the same view in game development.

    1) Is it considered bad practice to pass around interfaces or base classes, check the type then cast them to their concrete or derived class?
    2) If it is poor design, what are the better alternatives?
     
  2. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    When using interface logic a specific implementation is achieved in the concrete implementation of an interface method , not in a unique method name on the class itself. It seems you are only using the interface for datatyping?
     
  3. THoeppner

    THoeppner

    Joined:
    Oct 10, 2012
    Posts:
    205
    Its really bad practice to cast objects downwards. Sometimes its necessary but in general thats a hint for a bad design.

    If you're using interfaces you should keep them small and simple with just specific methods. Than you don't have to cast downwards.

    For example

    Code (csharp):
    1.  
    2.  
    3. interface Attacker
    4. {
    5.     void Attack();
    6. }
    7.  
    8. public class SwordFighter : Attacker
    9. {
    10.     public void Attack()
    11.     {
    12.     }
    13. }
    14.  
    15. public class BowFighter : Attacker
    16. {
    17.     public void Attack()
    18.     {
    19.     }
    20. }
    21.  
    22.  
    Maybe you're thinking: Ok each attacker should also be moveable, so adding a Move method to the interface is a good idea. Than you start to mix things and sometime you come to the point when you need to downcast.
     
  4. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    As you already identified in a different environment ( game development ) specifically Unity, you have to make best with the tools and structures at hand. Unity is a component driven architecture and GetComponent method does not understand interfaces. Base/abstract classes are you friend here ( one level of inheritance is not evil ) , then there is also a tag system to make use of for filtering ..etc..

    Not that I have personally found the holy grail balance in when to use what in Unity but I can say a pure OOP outlook of how to do things is not always fitting the task at hand, another example is the GUI inspectors for Monobehaviours . Which is better .. to sniff for another type of component via code or to inject the dependency via drag and drop both have pro and con arguments.

    Just thought I would add this as more food for thought.
     
  5. probbins

    probbins

    Joined:
    Oct 19, 2010
    Posts:
    216
    I think my question may have been slightly misunderstood, it wasn't anything to do with interfaces as in the code example you can easily change the interface to abstract class and implementation to inheritances.

    I'll try to reword the question.

    If you have a group of classes which represent different things, but are derived from the same class, but depending on what it represents may get used differently which may even required accessing the derived classes specific properties and methods, how is the best way to achieve this?

    Examples:

    You send a message across the network, all the messages are derived from abstract class NetworkMessage, each different type of message needs its own implementation, but each NetworkListener has a method for OnMessageRecieved(NetworkMessage message) which is the base class, in order to get the additional information, its required to be explicitly cast (unboxed).

    Code (csharp):
    1.  
    2. public class NetworkMessage {
    3.     public int id;
    4. }
    5.  
    6. public class MoveCommand : NetworkMessage {
    7.     public Vector3 position;
    8.     public List<int> units;
    9. }
    10.  
    11. public class SomeClass : Monobehaviour, NetworkListener {
    12.  
    13.     public void OnNetworkMessageRecieved(NetworkMessage message) {
    14.         if(message is MoveCommand) {
    15.             // Do move logic
    16.         }
    17.     }
    18. }
    19.  
    Another example could be selecting units on a map, you might pass around the base class to other systems, but at some stage depending on what you have selected you might need to do something more specific found in derived class.

    If using interfaces or inherited classes in this manner is considered bad practice, what is the better alternative?
     
    Last edited: Nov 12, 2013
  6. THoeppner

    THoeppner

    Joined:
    Oct 10, 2012
    Posts:
    205
    In that case use method overloading. So each derived class uses its own implementation of the method. You can also make the method abstract so that each derived class has to override the method.

    Code (csharp):
    1.  
    2.  
    3. public class NetworkMessage {
    4.     public int id;
    5.  
    6.     public virtual void DoSomething() {}
    7. }
    8.  
    9. public class MoveCommand : NetworkMessage {
    10.     public Vector3 position;
    11.     public List<int> units;
    12.  
    13.     public override void DoSomething() {
    14.         // Do the moving
    15.     }
    16. }
    17.  
    18. public class SomeClass : Monobehaviour, NetworkListener {
    19.     public void OnNetworkMessageRecieved(NetworkMessage message) {
    20.         message.DoSomething();
    21.     }
    22.  
    23. }
    24.  
     
  7. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    As THoeppner displayed is correct.

    You could also have additional data added to notifications like commands for instance as is seen in many event systems. You dont only need to derive alternate behaviour instructions via Datatypes and overloaded methods otherwise every single new behaviour requires a new type of subclass.

    This is subjective to preference.. many strictly proper OOP structures have an ill side effect of resulting in verbose code unfortunately.
     
  8. probbins

    probbins

    Joined:
    Oct 19, 2010
    Posts:
    216
    What about if you just wanted the data? e.g. position and units. You may not even need any field or method it contains, you just want to do something IF it is of a specific derived type e.g EndTurnCommand.
     
  9. probbins

    probbins

    Joined:
    Oct 19, 2010
    Posts:
    216
    Another way to word the question would be how is a good way to pass around a lot of different object types all under one object then act on them depending on what they originally were.
     
  10. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    If you've got base-class specific behaviour why don't you use virtual methods? That's specifically what they're for.

    The approach you're taking ensures that a huge amount of the code that you write needs to be written with explicit knowledge of a code hierarchy elsewhere. You're not using a class hierarchy to encapsulate derived behaviour, instead you're scattering it all over the place. It's a nightmare for maintainability, and it's probably quite error prone, and it's throwing away the benefits of things like type safety. To use a metaphor, you're using a hammer to bash a screw.

    As an alternative, why not provide a "DoLogic()" abstract method in NetworkMessage? Each type of NetworkMessage then has to implement a concrete version, and your SomeClass no longer needs a huge stack of if (...) { } else if (...)... bits of code. It just calls the high-level "DoLogic()", and the internals of your class structure will work in your favour instead of against it.
     
  11. probbins

    probbins

    Joined:
    Oct 19, 2010
    Posts:
    216
    In the example of the network message, the message is just a simple data class which doesn't actually perform any logic.

    Also something you want may be specific to particular derived class for example, you select eg you select a entity (eg could be a structure, environmental object, unit, enemy unit etc) the GUI lets say wants to only display the skill bar if its a unit / enemy unit etc. The UI doesn't necessarily want to make the entity perform any logic.

    You could just pass the gameobject through then do getcomponent but that would be surely slower than unboxing?

    Essentially whats a good design for being able to handle not know what something is specifically at runtime, but checking then handle accordingly.

    I still kind of feel like my question is being misunderstood but perhaps its me misunderstanding the answers ;)
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    That's what it is now, but you're asking us what to do differently, and that's one suggestion. If you've got behaviour specific to a given class then best practice is to have it in that class. Otherwise you have to spaghetti it around and have to start infecting other classes with explicit knowledge of how that class or class structure works.

    In the example of the GUI showing different stuff based on what unit is selected, I'd personally consider either having those as properties of a single unit class, or having the unit class have simple logic that informs that GUI that something changed and then letting the GUI handle it. After all, I wouldn't expect a unit to make decisions about what the GUI shows because, again, that's spaghetti-ing the functionality around.

    For instance, selecting the unit might call a generic "Select" method. That method might fire an "OnSelected" event. Your GUI listens for OnSelected events and responds to them accordingly.
     
    Last edited: Nov 13, 2013
  13. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    The short answer is to use polymorphism where it's appropriate and to avoid the situation any other time. It is absolutely something that can be designed for, but you will have to change more than one thing in your design to make it work. It's a holistic approach question, not a specific case question.
     
  14. probbins

    probbins

    Joined:
    Oct 19, 2010
    Posts:
    216
    Thanks for the discussion.

    Let say you also have buildings that can be selected as well, would you use OnSelected event for both then have the GUI determine which was selected and act accordingly or a separate one for each then listen to both.

    Also lets say you have your unit selected, you then right click on another entity, if it is a structure you want to occupy it, if its an allied unit follow, if it is a enemy unit attack etc. I would have thought passing the entity to the unit then having the unit determine what it is and act accordingly be straightforward, understandable and relatively cheap, how you would approach it?
     
    Last edited: Nov 14, 2013
  15. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    How is a building different to a unit?

    There are no one-shot correct answers to these things. Design is about problem solving, and problem solving is about understanding the whole of the thing and the context it is within.

    I would start by figuring out the kinds of things I wanted all of my different entities to be able to do. Then I'd figure out the data they each needed to be able to do it. Then I'd shuffle the whole lot around so that like things are somehow grouped together, but in the simplest way possible, with generic methods and data that cover as much as possible. I'd possibly try that a few times to see how things ultimately fit best.

    You're trying to design a system, but you're asking relatively low level implementation questions to do it. You're asking things like "what happens when I click..." instead of "what interactions does my system need to support" and working down from there.
     
  16. FultonX

    FultonX

    Joined:
    Sep 7, 2014
    Posts:
    5
    Here's the answer (to this particular question at least) about casting downwards for network messages to get your custom data: https://docs.unity3d.com/ScriptReference/Networking.NetworkMessage.ReadMessage.html

     
  17. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
  18. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I'm going to necro this thread worse than it already has been, because I ran into a similar situation and stumbled across it.

    In my situation:

    I have a C# server, not unity.
    In a shared assembly (shared between Unity and the server), it has NetMessage defined, a base class. This is shared so both the client and server know about certain shared network messages.

    One of its derived classes is LoginRequest. LoginRequest has two properties, the login id and the password.
    When the client wants to login to the server, it creates and sends a new LoginRequest(login, password) to the server.

    Now, the server receives the network data, and has to create a loginrequest message for login handling by the server.
    Code (CSharp):
    1.  
    2.         private static NetMessage ReadMessage(NetPeer peer, NetDataReader reader)
    3.         {
    4.             short rawMessageType = reader.GetShort();
    5.             MessageType messageType = (MessageType)rawMessageType;
    6.             NetMessage message;
    7.             switch (messageType)
    8.             {
    9.                 case MessageType.None:
    10.                     message = new NullMessage();
    11.                     break;
    12.  
    13.                 case MessageType.LoginRequest:
    14.                     message = new LoginRequest();
    15.                     break;
    16.                 default:
    17.                     message = new NullMessage();
    18.                     CLog.Log($"Unknown message type {rawMessageType} received from {peer.EndPoint}");
    19.                     break;
    20.             }
    21.  
    22.             message.Read(reader);
    23.             return message;
    24.         }
    so it creates the concrete message type, and calls .Read() to actually read in the byte data from the network stream or whatever, and populate an actual login message. But this function returns it as a NetMessage, NOT a LoginRequest.
    Why? I want to handle the Read function for all, in one place, instead of having to handle the serialization in each case statement.

    When it returns it, I need to actually handle the login request.
    This is where I think one of the above posters is touching on.
    So I have a NetMessage now, that is actually a base type of some derived type. So now I want to actually do the login work.


    Code (CSharp):
    1.           switch (message.Type)
    2.             {
    3.                 case MessageType.None:
    4.                     break;
    5.                 case MessageType.LoginRequest:
    6.                     HandleLoginRequest(peer, (LoginRequest) message);
    7.                     break;
    So now I have to cast it BACK to a LoginRequest, because this is on the server. I do NOT want to have the actual login handling code in the LoginRequest, because that is available to the client. I want to call a method on the server, and actually pass it a LoginRequest, so it can access the login id and password.

    I don't see how I could have a generic way to handle DoWork() on the NetMessages, unless I want to have both messages defined, one for the client and one for the server. That's the only way I could see to handle that. The client one is basically a data class, the server one actually has the login handling code.

    So...a viable way of handling it, personally I don't care for the duplication and so far I feel just downcasting is fine in this case. These messages only transport data, they don't handle processing the results.
     
  19. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Usually when you have a server and a client... you won't have the same actual libraries on both for such things.

    These are called 'Data Contracts', the type that is transferred between client and server are pure data, very limited on the code.

    Check out something like WCF (windows communication foundation, it actually uses the terminology DataContract). When you define web methods on your WCF interface, then host the web service, a client can request from the server the data contract information. In doing so it can build its own data types that serialize to what the server expects.

    They'll often be named the same thing... but are distinct class types on the client side.

    Any implementation, non-data, is then implemented apart from these datacontracts.

    You might have a 'LoginRequest' and a 'LoginRequestResolver' class distinct from one another, one for data, the other for handling that data.
     
  20. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Yeah, I get the WCF environment, however I'm using a different means of communications.

    In this case, the only reason I see for having two different ones are 1. To avoid downcasting, and 2. to put the logic to handle a login request inside the message, which I personally wouldn't choose to do anyway.
    It seems if I am just sending a login request from the client to the server, why not let both know about the login request message type if all it is is a message envelope, a data class. I'd prefer to have some other object in place to handle the actual login resolution.
     
  21. BitCrushed

    BitCrushed

    Joined:
    Dec 2, 2015
    Posts:
    75
    i think i know what the original question was about , if i am correct this user/s were talking about having a lot of different
    classes representing each a derived type from a base type/class like the general usage of abstract classes , and the question if i understood correct , was , how to know what type of derived class was used since all of them use the same baseclass :
    i had the same issue , which i dealt with in one of my projects and i will submit an example here to demonstrate this:
    Code (CSharp):
    1. public abstract class base_firearm:MonoBehaviour
    2. {
    3. //...
    4. }
    here we have the abstract baseclass of a firearm which i use to derive different firearms from ,for example:
    Code (CSharp):
    1. public class firearm_shotgun:base_firearm
    2. {
    3.    
    4.     public firearm_shotgun()
    5.     {
    6.         deals_damage=20;
    7. //.....
    8.     }
    9.    
    10.     void Awake()
    11.     {
    12.     }
    13. }
    now , imagine having 10 different firearms ,all deriving from base_firearm,and now i have a factory class that handles and creates all of my game's objects, i need a firearm of the firearm_shotgun created in my player class , but i want a single function in my factory class to handle any type of firearm based on base_firearm as input , so i know my class is derived from base_firearm but i need to know which firearm was passed in ,so i can create the correct one, therefore i check up the type of firearm that was passed in:

    Code (CSharp):
    1. public T Add_FireArm<T>(GameObject caller) where T:base_firearm
    2.     {
    3.         System.Type type=typeof(T);
    4.         if(type== typeof(firearm_pistol))
    5.         {
    6.             return (T)(object)caller.AddComponent<firearm_pistol>();
    7.         }
    8.         else if(type== typeof(firearm_shotgun))
    9.         {
    10.             return (T)(object)caller.AddComponent<firearm_shotgun>();
    11.         }
    12.         else if(type== typeof(firearm_smg))
    13.         {
    14.             return (T)(object)caller.AddComponent<firearm_smg>();
    15.         }
    16.         throw (new System.ArgumentException());
    17.     }
    so i am now able to call:
    Code (CSharp):
    1. private firearm_shotgun shotgun;
    2. //...
    3. shotgun=Factory.Instance.Add_FireArm<firearm_shotgun>(gameObject);
    this is essentialy what i believe the question was about ...

    heres another example:
    Code (CSharp):
    1. public class gevent_take_damage :GameEvent
    2. {
    3.     public GameObject go;
    4.     public Vector3 hit;
    5.     public int amount;
    6.  
    7.     public gevent_take_damage(GameObject _go,Vector3 _hit,int _amount)
    8.     {
    9.         go=_go;
    10.         hit=_hit;
    11.         amount=_amount;
    12.     }
    13. }
    14.  
    a game event system ,where each different event is derived from GameEvent , now in my factory ,again i want a single function to handle the creation and dispatching of all the different event types derived from GameEvent, so therefore:
    Code (CSharp):
    1. public void Dispatch_Event<T>(params object[] param)
    2.     {
    3.         System.Type type=typeof(T);
    4.  
    5.         if(type == typeof(gevent_use_equiped))
    6.         {
    7.             //find object type param
    8.             for(int i=0;i<param.Length;i++)
    9.             {
    10.                 if((param[i] is App_Globals.ObjectTypes))
    11.                 {
    12.                     var objtype=(App_Globals.ObjectTypes)param[i];
    13.  
    14.                     gevent_use_equiped my_event=new gevent_use_equiped(objtype);
    15.                     EventMessenger.Instance.Raise(my_event);
    16.                     return;
    17.                 }
    18.                 else continue;
    19.             }
    20.  
    21.         }
    22.         else if(type == typeof(gevent_change_equiped))
    23.         {
    24.             //find object type param
    25.             for(int i=0;i<param.Length;i++)
    26.             {
    27.                 if((param[i] is App_Globals.ObjectTypes))
    28.                 {
    29.                     var objtype=(App_Globals.ObjectTypes)param[i];
    30.                     gevent_change_equiped my_event=new gevent_change_equiped(objtype);
    31.                     EventMessenger.Instance.Raise(my_event);
    32.                     return;
    33.                 }
    34.                 else continue;
    35.             }
    36.            
    37.         }
    38.  
    39.         else if(type==typeof(gevent_take_damage))
    40.         {
    41.             GameObject target=null;
    42.             Vector3 hit=Vector3.zero;
    43.             int amount=-1;
    44.  
    45.             for(int i=0;i<param.Length;i++)
    46.             {
    47.                 if((param[i] is GameObject))
    48.                 {
    49.                     target=(GameObject)param[i];
    50.                 }
    51.                 else if((param[i] is Vector3))
    52.                 {
    53.                     hit=(Vector3)param[i];
    54.                 }
    55.                 else if((param[i] is int))
    56.                 {
    57.                     amount=(int)param[i];
    58.                 }
    59.                 else continue;
    60.             }
    61.  
    62.             gevent_take_damage my_event=new gevent_take_damage(target,hit,amount);
    63.             EventMessenger.Instance.Raise(my_event);
    64.         }
    65.        
    66.     }
    67.  
    which is easily called again like :
    Code (CSharp):
    1. Factory.Instance.Dispatch_Event<gevent_use_equiped>(equiped);
    2. //...
    3. Factory.Instance.Dispatch_Event<gevent_change_equiped>(App_Globals.ObjectTypes.Shotgun);
    4. //..
    5. Factory.Instance.Dispatch_Event<gevent_take_damage>(App_Globals.PlayerManager.gameObject,damage,Vector3.zero);
    thats how i solved mine , but maby i am off base here .

    regards..
     
    NeatWolf likes this.