Search Unity

GetComponents: Possible to use with C# Interfaces?

Discussion in 'Editor & General Support' started by KyleStaves, Sep 17, 2010.

  1. KyleStaves

    KyleStaves

    Joined:
    Nov 4, 2009
    Posts:
    821
    Attempting to do something like:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public interface ICollide{
    6.     void PlayerHit(ControllerColliderHit hit);
    7. }
    8.  
    Code (csharp):
    1.  
    2.     void OnControllerColliderHit(ControllerColliderHit hit){
    3.         ICollide[] hitColliders = (ICollide[])hit.gameObject.GetComponents(typeof(ICollide));
    4.        
    5.         for (int i = 0; i<hitColliders.Length; i++){
    6.             hitColliders[i].PlayerHit(hit);
    7.         }
    8.     }
    9.  
    Results in:

    Code (csharp):
    1.  
    2. nvalidCastException: Cannot cast from source type to destination type.
    3. PlayerController.OnControllerColliderHit (UnityEngine.ControllerColliderHit hit)   (at Assets/Scripts/Systems/Player/PlayerController.cs:52)
    4. UnityEngine.CharacterController:Move(Vector3)
    5. UnityEngine.CharacterController:Move(Vector3)
    6. PlayerController:Update() (at Assets/Scripts/Systems/Player/PlayerController.cs:47)
    7.  
    -----------------

    The same function and syntax works fine for getting classes, just not interfaces. Does anyone know how to use GetComponents with C# interfaces? [/code]
     
    tonytopper likes this.
  2. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Its called GetComponent not GetSomething ;)
    That should already answer it

    as such no, it won't work with interfaces, it will only return classes of type Component or extending it (Behaviour and especially MonoBehaviour on which your whole functionality bases)

    as for the technicals on why: cause your class does not register itself with the engine nor is it managed by it, but all components+ are.
    You can replicate that by making an own manager that handles specific things of yours where interface implementing classes must register themself with your manager
     
    Lewie4 likes this.
  3. KyleStaves

    KyleStaves

    Joined:
    Nov 4, 2009
    Posts:
    821
    GetComponent actually does work with interfaces :wink:, just not GetComponents :(

    Considering whatever the underlying functionality of GetComponent(s?) does work with interfaces, I was hoping it was some sort of syntax issue. Odd that GC works but GCs does not; oh well. In this particular case I don't really need to work around it, I'll just make the interface a MB extended class instead. Was mostly just curious for my other project which makes extensive use of interfaces.

    A relatively easy workaround, for anyone interested, is to use GetComponent to return a list of all components on an object, and cast them to your interface.

    Code (csharp):
    1.  
    2.         Component[] hitComponents = hit.gameObject.GetComponents(typeof(ICollide));
    3.        
    4.         Debug.Log(hitComponents.Length);
    5.        
    6.         for (int i = 0; i<hitComponents.Length; i++){
    7.             if (hitComponents[i] is ICollide){
    8.                 ICollide colInterface = hitComponents[i] as ICollide;
    9.                 colInterface.PlayerHit(hit);
    10.             }
    11.         }
    12.  
    It should only be returning components which implement the interface (in this case, ICollide); however, I'd do a quick if (result is interface) anyway just to be sure.
     
  4. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Really? I can't get that to work. I wouldn't expect it to, but it would be great if it did work.
    Code (csharp):
    1. //This does not work:
    2. Collider hitcollider = hitInfo.collider;
    3. IArmored v = hitcollider.GetComponent<IArmored> ();
    I get an error.
    The type `IArmored' must be convertible to `UnityEngine.Component' in order to use it as parameter `T' in the generic type or method `UnityEngine.Component.GetComponent<T>()'

    Do you have to derive the Interface from something specific? Anyone have a working example?

    BTW, this is how I currently do it, where Entity derives from Component and IArmored is an Interface. Maybe there is a better way/
    Code (csharp):
    1. IArmored a = hitcollider.GetComponent<Entity> () as IArmored;
    2. if (a != null) {
    3.    Debug.Log("Projectile hit IARMORED";            
    4. }
     
    Last edited: Jan 13, 2011
  5. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Getcomponent will only find stuff that is of class component or extends it.
     
  6. teatime

    teatime

    Joined:
    Jun 16, 2008
    Posts:
    129
    the old-school non-generic version definitely finds interfaces. pakfront, you have to do it like this:
    Code (csharp):
    1. IAttribute attribute = GetComponent(typeof(IAttribute)) as IAttribute;
    and then
    Code (csharp):
    1. attribute.DoAttributeStuff();
    should work just fine.
     
    Last edited: Jan 22, 2011
    Novack and AbgaryanFX like this.
  7. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Get can impossibly work as you can't assign IAttribute as a component cause it just is none unless there is a bug in 3.1 that allows stuff not derived from Component to be assigned as one

    Also it sounds like an extremely clumsy and low performance solution for a problem where a trivial statc LinkedList<IAttribute> attributes = new LinkedList<IAttribute>(); in Attribute would do it too and that at a higher performance cause it requires no filtering etc

    GetComponent(s) is still one of the slowest functions of all and on mobile too heavy to do it in realtime each frame for example
     
  8. teatime

    teatime

    Joined:
    Jun 16, 2008
    Posts:
    129
    the attribute class i initially posted was a bit of a kludge and only necessary for situations where you needed to grab multiple components, which i hadn't realized kyle had already covered the concept of. but this works, and it's a pretty nice "bug" as designing for interfaces is far less clumsy than for individual components, and it can't be slower than caching any other component reference from GetComponent as plenty of Unity developers see fit to do. i'd be interested to hear from someone at Unity if this functionality is intentional or not.
     
    tonytopper likes this.
  9. Rafes

    Rafes

    Joined:
    Jun 2, 2011
    Posts:
    764
    I've posted a few places about this tonight. I'm just hoping someone has an idea. I need to get the component which implements an interface. This is in an OnTriggerEnter(Collider other). Any ideas?

    I'm open to any pattern, even if it doesn't use GetComponent. I could use a class but it would have to be spliced in to a class tree at the uppermost level since .Net doesn't support multiple inheritance. We want to release this tool to the Unity-public, so an Interface is by far the most elegant way to instruct people to snap on this solution.

    Cheers,
     
  10. rstehwien

    rstehwien

    Joined:
    Dec 30, 2007
    Posts:
    101
    If you wanted to work with a typesafe array of interfaces this will work:
    Code (csharp):
    1.  
    2. void Start () {
    3.     ITest[] tests = ConvertToArray<ITest>(GetComponents(typeof(ITest)));
    4.     Debug.Log(tests.Length, this);
    5.  
    6.     ITest test = GetComponent(typeof(ITest)) as ITest;
    7.     Debug.Log(test != null ? "Found ITest" : "ITest Not Found", this);
    8. }
    9.  
    10. public static T[] ConvertToArray<T>(IList list)
    11. {
    12.     T[] ret = new T[list.Count];
    13.     list.CopyTo(ret, 0);
    14.     return ret;
    15. }
    16.  
     
    LapidistCubed and RandyLarson like this.
  11. nickudell

    nickudell

    Joined:
    May 26, 2013
    Posts:
    1
    I would recommend that anybody considering doing the above take a look at the MSDN article on Extension Methods
     
    meshflakes and Menion-Leah like this.
  12. Xaon

    Xaon

    Joined:
    Feb 28, 2013
    Posts:
    62

    If anybody curious on Unity 4.1 it still works. The only obvious thing is the game object must have component that implements particular interface.
     
  13. mohydineName

    mohydineName

    Joined:
    Aug 30, 2009
    Posts:
    301
    Works for me in 4.2

    Stephane
     
  14. Andy2222

    Andy2222

    Joined:
    Nov 4, 2009
    Posts:
    81
    Btw here is a linq extension version:

    Code (csharp):
    1.  
    2.     public static T GetInterface<T>(this GameObject inObj) where T : class
    3.     {
    4.         return inObj.GetComponents<Component>().OfType<T>().FirstOrDefault();
    5.     }
    6.  
    7.     public static IEnumerable<T> GetInterfaces<T>(this GameObject inObj) where T : class
    8.     {
    9.         return inObj.GetComponents<Component>().OfType<T>();
    10.     }
    11.  
    Here are more restrictive versions that actually check if <T> is a interface and wont allow classes to return!
    Code (csharp):
    1.  
    2.     public static T GetInterface<T>(this GameObject inObj) where T : class
    3.     {
    4.         if (!typeof(T).IsInterface) {
    5.             Debug.LogError(typeof(T).ToString() + ": is not an actual interface!");
    6.             return null;
    7.         }
    8.  
    9.         return inObj.GetComponents<Component>().OfType<T>().FirstOrDefault();
    10.     }
    11.  
    12.     public static IEnumerable<T> GetInterfaces<T>(this GameObject inObj) where T : class
    13.     {
    14.         if (!typeof(T).IsInterface) {
    15.             Debug.LogError(typeof(T).ToString() + ": is not an actual interface!");
    16.             return Enumerable.Empty<T>();
    17.         }
    18.  
    19.         return inObj.GetComponents<Component>().OfType<T>();
    20.     }
    21.  

    If u restrict it to Component or just Monobehaviours is up to u. I suspect thats essentially the same that the none generic version for GetComponents(type) does, i rather use a clean named function than relying on the "strange" behavior of GetComponents, since the generic and none generic version should behave the same.

    bye Andy
     
    Last edited: Sep 5, 2013
  15. Nition

    Nition

    Joined:
    Jul 4, 2012
    Posts:
    781
    This is a nice hidden feature! Still working in 4.5. This thread is also serves as a good lesson in testing your assumptions before you post.
     
  16. Helical

    Helical

    Joined:
    Mar 2, 2014
    Posts:
    50
    2014, This feature is still available :). I was able to Invert Control so that I can attack new funcitonality dynamically without changin old legecy source code.
     
  17. AbgaryanFX

    AbgaryanFX

    Joined:
    Jan 9, 2010
    Posts:
    167
    It WORKS in unity 4.5.3! :)
    thanks teatime ;)
     
  18. Nition

    Nition

    Joined:
    Jul 4, 2012
    Posts:
    781
    Now implemened in Unity 4.6 apparently. From the release notes:
    • Added smart-allocating GetComponents<List> method which fetches components of type T and grows the list as needed. Non-generic version that supports interfaces has also been added.
     
    Last edited: Nov 26, 2014
  19. RakshithAnand

    RakshithAnand

    Joined:
    Jun 30, 2013
    Posts:
    56
    Thats awesome! thanks :)
     
  20. JohnAraujo

    JohnAraujo

    Joined:
    Aug 8, 2015
    Posts:
    1
    There's nothing in the documentation regarding usage of this non-generic GetComponents. Has it been removed?
    Edit: Figured it out, now I'm just gonna post a quick breakdown for posterity--
    This doesn't work:
    Code (csharp):
    1. IActionable[] interfaces = gameObject.GetComponents<IActionable>();
    Where IActionable is an interface you've defined. This DOES work:
    Code (csharp):
    1. List<IActionable> Actionables
    2. Component[] comps = gameObject.GetComponents(typeof(IActionable));
    3. foreach (Component com in comps)
    4.     Actionables.Add(com as IActionable);
     
    Last edited: Aug 20, 2015
  21. mr-gelmir

    mr-gelmir

    Joined:
    Dec 4, 2012
    Posts:
    4
    Stumbled on this thread while looking for ways to make unity work with interfaces. Contrary to what the previous poster writes, for me (in Unity 5.1) both these calls work now.

    Code (CSharp):
    1. foreach (var item in GetComponents<IInterface>())
    2.             {
    3.                 Debug.Log(item);
    4.             }
    5.  
    6.             IGridMover[] interfaces = gameObject.GetComponents<IInterface>();
    7.             Debug.Log(interfaces.Length);
    Thanks, Unity
     
  22. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    If anyone is curious about how much slower this is compared to GetComponent:

    the results are pretty much the same everytime i run it, takes about 150% more time
    upload_2016-8-13_18-52-32.png

    This is my Testcode, i invoked both methods at the end because i thought the compiler might remove them if they are not used
    Code (csharp):
    1.  
    2. if(Input.GetKeyDown("0")){  
    3.  
    4.     float start;
    5.        
    6.     start = Time.realtimeSinceStartup;
    7.     HitManager hitM = null;
    8.     for (int i= 0; i< 10000; i++){ hitM = Main.actorController.GetComponent<HitManager>();    }
    9.     print("GetComponent took:\t"+(Time.realtimeSinceStartup-start));
    10.  
    11.     start = Time.realtimeSinceStartup;
    12.     IDamageable<Attack, Vector3> HitInterface = null;          
    13.     for (int i= 0; i< 10000; i++){ HitInterface = Main.actorController.GetComponent<IDamageable<Attack, Vector3>>();    }
    14.     print("GetInterface took:\t"+(Time.realtimeSinceStartup-start));
    15.        
    16.     hitM.Hit(AttackManager.playerThrust, Vector3.forward, Faction.Player);
    17.     HitInterface.Famage(AttackManager.playerThrust, Vector3.forward);
    18. }
    19.  
     
  23. tonytopper

    tonytopper

    Joined:
    Jun 25, 2018
    Posts:
    226
    For those dropping here via Google searches, I wouldn't trust the performance code above as the interface is using Generics and that may be what's negatively affecting the performance, not the fact that it's an Interface.
     
    behzatbabil and ZachHelm like this.