Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Writing generic code

Discussion in 'Scripting' started by vee41, May 31, 2015.

  1. vee41

    vee41

    Joined:
    Nov 14, 2013
    Posts:
    32
    Hi!

    This is fairly basic C# question but I cannot grasp my head around a nice clean way of doing this.

    I have few classes, ie. Car, Man, Tree etc.
    Different things that do very different stuff.

    I'd like to have a function that I can toss one of those classes and it would perform some generic operations on it based on interface they all implement. What I'd also need is to save reference to the actual base class.

    I am implementing a 'generic' button, so when someone presses the button the button will send it's content (reference to one of the classes above) to an UI manager that will do things with it. I am having problems finding the proper expressions to save the reference of the class in generic way, I do not want my button to be like this:

    Code (CSharp):
    1. class Button {
    2.  
    3. Car car;
    4. Tree tree;
    5. Man man;
    6.  
    7. OnClick()
    8. {
    9. if (car)
    10.     UIManager.ClickedButton(car);
    11.  
    12. if(tree)
    13.     UIManager.ClickedButton(tree);
    14.  
    15. ...
    16.  
    17. }
    18. }
    Instead I'd like something along the lines of :

    Code (CSharp):
    1. class Button {
    2.  
    3. IInterface content ( or <T> content )
    4.  
    5. OnClick()
    6. {
    7.  
    8.     UIManager.ClickedButton(content);
    9.  
    10. }
    11. }
    I'd need a way to have generic content on the button that I can forward to UIManager and UIManager can identify what kind of content it just received and do things based on that. Perhaps there is an more straight forward way to handle stuff like this than my approach?
     
  2. Jamster

    Jamster

    Joined:
    Apr 28, 2012
    Posts:
    1,102
    Sounds like you want an interface, essentially they all implement an interface like IDoSomethingable and then you can perform set operations on anything IDoSomethingable.
    Code (csharp):
    1.  
    2. interface IDoSomethingable
    3. {
    4.     void DoSomething();
    5. }
    6.  
    7. class Man : Monobehaviour, IDoSomethingable
    8. {
    9.     public void DoSomething()
    10.     {
    11.         Debug.Log("Man does something");
    12.     }
    13. }
    14.  
    15. class Tree : Monobehaviour, IDoSomethingable
    16. {
    17.     public void DoSomething()
    18.     {
    19.         Debug.Log("Tree does something");
    20.     }
    21. }
    22.  
    23. class Manager : Monobehaviour
    24. {
    25.     IDoSomethingable something;            //This can now be set to a man or a tree object
    26. }
    27.  
    Untested
     
    erebel55 and vee41 like this.
  3. vee41

    vee41

    Joined:
    Nov 14, 2013
    Posts:
    32
    Thanks for the reply! I do have an interface I'd like to use, but I also need to get the actual type of the class that implements the interface at the end of the pipe. Here is big picture what I am trying to accomplish:

    UIPanel (holds various buttons, things and other stuff)
    UIManager (manages all clicks and stuff)
    Button (object on the UIPanel)

    I have UIPanel for each my classes, so UIPanelTree knwos how to display data of Tree class that it is pointed to, UIPanelMan knows how to display data of Man class it is pointed to and so forth.

    What I need to happen is this: When a button is clicked it needs to forward it's content to the UIManager. UIManager checks what type of content was received and acts based on that. If button of type Tree it will open UPanelTree and pass the content on to that so the UIPanel knows which trees data to display.

    So something like this:
    UIPanelTree.Display(Tree tree) -> DrawButtons(set button 'content' as generic variable or similar) -> Button.OnClick(<T> content) -> UIManager.ButtonWasClicked(<T> content) -> Open proper panel based on <T> type of content.

    So I need the button not to be too specific on what kind of data it holds, and just pass it on to the UI Manager which can 'unwrap' the actual type of the content. I don't know if this is possible with Interfaces (get actual type through interface) or generics (cast as actual type of the class). I can work around this with ugly solutions, but I am looking for the elegant solution which I believe would have soemthign to do with Generics. Just can't find right way to express it code wise. :)
     
  4. vee41

    vee41

    Joined:
    Nov 14, 2013
    Posts:
    32
    I managed to sort of solve this with some casting. I pass my objects as IInterface to button, and button delivers them in that format to the panel that needs the content. The panel then casts it explicitly like this:

    Tree data = (Tree)Button.content; // content is IInterface that Tree implements

    I still need to have GetType() method in IInterface that returns enum value holding the actual type of data so I know what kind of content button holds. A bit ugly, but works and achieves what I want with quite small amount of bubblegum code.
     
  5. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    I think you're missing the point of interfaces if you have functions that "do something" in the actual interface itself... unless you're talking "user interface" rather than "computer science interface"?
     
    vee41 likes this.
  6. blizzy

    blizzy

    Joined:
    Apr 27, 2014
    Posts:
    775
    Why would your panel have the need to cast it back to a specific type? This completely defeats the benefit of using an interface in the first place.
     
    vee41 likes this.
  7. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    With an interface you don't need to know the type. Let's say you have this:

    Code (csharp):
    1.  
    2. public interface IButtonObject {
    3.    void OpenPanel();
    4. }
    5.  
    Now you have your tree and man classes:
    Code (csharp):
    1.  
    2. public class Man : IButtonObject
    3. {
    4.     public void OpenPanel()
    5.     {
    6.           //Open the man panel
    7.     }
    8. }
    9.  
    Code (csharp):
    1.  
    2. public class Tree : IButtonObject
    3. {
    4.     public void OpenPanel()
    5.     {
    6.           //Open the tree panel
    7.     }
    8. }
    9.  
    Your Button content would just be of type IButtonObject so you'd only have to cast to IButtonObject and then call the OpenPanel method. You'd never need to know what the concrete type was as the CLR knows that already underneath and will call the correct concrete type function.

    One word of warning though... if you need to serialize this data I've been seeing Interfaces do some wonky stuff with IL2CPP so you may be better off taking a base class approach.
     
    vee41 likes this.
  8. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    An object always has a reference to it's actual base class. The object knows what it is better than the compiler, so you can always ask the object for its type: .GetType(). Or, you can just tell the object to take a certain action and it will complete that action based on its own definition. This is the power of inheritance.
    This is interesting because you say you would like to complete some kind of "generic operations" but it sounds like you will then be doing UI stuff, which is typically not generic. Anyways, perhaps you are doing both some generic stuff and some not generic stuff - that is okay..
    Now things are a little more clear. I like to call these activities/tasks that are taken on by different classes "responsibilities". I think you have smartly segregated the responsibilities of "modeling" (your base classes) and viewing (your UI Panels) but are now left with the awkward responsibility of mapping between these segregations. Whether you intended or not, you have followed a well known pattern in software called Model-View-Controller (MVC). Well .. almost.

    Typically, MVC is used to view and act against distinct entities. The entities are modeled in the Model, the activities a user can make with the entity are controlled by the Controller, and the way the entities and available actions are displayed is defined in the View. You'll notice that the responsibility I was referring to earlier (the mapping) is missing from this description. That is because it is often hidden within an MVC framework as a registration process or through some other kind of magic such as naming conventions.

    So obviously, somebody needs to know how to map between Tree and UITreePanel. It's a bit ugly because if you are referring to these objects generically you will need to do type checking in order to map. E.g.,:
    Code (csharp):
    1. public UIPanel GetUIPanelForIGenericType(IGenericType genericType)
    2. {
    3.   if (genericType is Tree)
    4.     return new UITreePanel();
    5.   else if (genericType is Man)
    6.     return new UIManPanel();
    7.   // etc...
    8. }
    This isn't very pretty and worse, this class now has a dependency on every generic type and its panel PLUS, if you want to add new types, you will need to update this class. It's a bit of a nightmare.

    An alternative is to have your generic type know about it's UIPanel or vice versa. Then the manager class can simply ask the generic class for its UIPanel and through the power of inheritance, the object will be able to answer based on its own declaration. E.g.,
    Code (csharp):
    1. interface IViewable
    2. {
    3.   public UIPanel GetPanel();
    4. }
    5.  
    6. class Tree : IViewable
    7. {
    8.   public override UIPanel GetPanel()
    9.   {
    10.     return new UITreePanel();
    11.   }
    12. }
    13.  
    14. class UIManager
    15. {
    16.   public void DisplayTheThing(IViewable theThing)
    17.   {
    18.     var panel = theThing.GetPanel();
    19.     panel.Display(theThing);
    20.   }
    21. }
    This is pretty clean but it makes the base class take on the additional responsibility of knowing about its UIPanel. This breaks the Single Responsibility Principle of well designed code.

    Another alternative, which is the solution I've seen in most of the MVC frameworks I've used is to have a registration process. This registration might be done declaratively in a configuration file. E.g.,
    Code (csharp):
    1. <config>
    2.   <typeMapping>
    3.     <add type="Tree" viewer="UITreePanel" />
    4.   </typeMapping>
    5. </config>
    The manager, or some other mapping class would then read in this configuration file and create the corresponding viewer class when it receives a reference to the Tree class. Of course, this still suffers from the "need to be updated whenever you add a new type" problem.

    A related solution uses automatic registration called "registration by convention". In this technique, the mapping class will guess at the name of the viewer class based on the name of the concrete implementation of IViewable.
    Warning, this example uses reflection and is written "from the hip".. It might be able to get you started in the right direction but won't compile.
    Code (csharp):
    1. class Tree : IViewable
    2. {
    3. }
    4.  
    5. class ViewableMapper
    6. {
    7.   public UIPanel GetPanelForViewable(IViewable viewable)
    8.   {
    9.     var simpleName = viewable.GetType().Name;
    10.     var likelyViewerName = string.Format("UI{0}Panel", simpleName);
    11.     var likelyViewerType = Assembly.GetAssembly(typeof(IViewable)).GetType(likelyViewerName);
    12.     if (likelyViewerType != null)
    13.       Activator.CreateInstance(likelyViewerType);
    14.     else
    15.       throw new CustomException("Could not find a UIPanel for {0}", simpleName);
    16.   }
    17. }
     
    blizzy and vee41 like this.
  9. vee41

    vee41

    Joined:
    Nov 14, 2013
    Posts:
    32

    Thank you for the very through and enlightening explanation! I did model this system a bit like MVC which I've used in some web development previously, while I am not familiar with more than the base concept it feels quite natural way to handle UI stuff.

    The registration processes you describe seems like the solution I was after, it did feel like I was approaching in way too rigid manner (passing data on) which caused lots of the unnecessary dependencies and problematic code you described. Registration is clean way to solve this.

    Also thank you for other people that respondend, my issue was described a bit vaguely so issue wasn't use of interfaces as it was more about data binding.