Writing an extension method for a class. Is it possible? How?

Hi,

I wrote this piece of code to speed up checking existance of singletons without actually changing the singletons implementation and without of course checking their Instance property or the non-accessible _instance private static variable.

using UnityEngine;
using System.Collections;

public static class FNSUtils {
	public static bool ExistsInSceneOrError(this System.Type type)
	{
		if (GameObject.FindObjectOfType(type) != null)
			return true;
		else
		{
			Debug.LogError("Couldn't find a " + type.ToString() + " in the scene. Add it, save the scene and reload it");
			return false;
		}
	}
}

In this way I could simply do a check like GameManager.ExistsInSceneOrError() and return a value, without the hassle to write this for every Manager or Singleton.

But, it doesn’t seem to work.

Now, is there a way to do this with the method extensions? Which other solution would you propose? A static utility class with a static method?

I assume you've tried: GameObject.FindObjectOfType(typeof(type))

"But, it doesn't seem to work." Need more details. Looks ok to me, at fist glance. Is it the FindObjectsOfType that fails? Alternatives: Create a Static Class that contains a Dictionary - and have your singletons register themselves in there. Then when you need to check for existence, it should be a much shorter list to look through than all scene objects, and you can just use dictionary.Contains to see if it exists. Though I'd also return the looked-up gameobject or null, rather than a bool.

3 Answers

3

Well, the way you have it defined now, no …

SomeClass.ExistsInSceneOrError()

will NOT give you access to this function. Note, you are NOT extending “SomeClass”, you are extending System.Type.
However,

typeof(SomeClass).ExistsInSceneOrError()  

SHOULD work, because the typeof() function will return a Systsem.Type variable.

Consider the extension method:

 static void func(this Object o)

Object o, must be an INSTANCE of a variable. There is no way to specify a TYPE as input in a regular parameter. Sure, you can use System.Type, but this will ALSO require and instance of a variable (of class System.Type), (which in the code above, is instantiated by typeof() ).

Extension methods are simply not meant to provide the syntax you are looking for BECAUSE they take as input; variable instances, not types.

Instead of Extension function, you could use Generics and Inheritance to get the syntax you want:

public abstract class SingletonBase<T>:MonoBehaviour where T: SingletonBase<T>
{
    public static bool ExistsInSceneOrError()
    {
        if (GameObject.FindObjectOfType(typeof(T)) != null)
            return true;
        else
        {
            Debug.LogError("Couldn't find a " + typeof(T).ToString() + " in the scene. Add it, save the scene and reload it");
            return false;
        }
    }
}

class SingletonOne : SingletonBase<SingletonOne> //formerly :Monobehavior
{
    //no change
}

class SingletonTwo : SingletonBase<SingletonTwo>  //formerly :Monobehavior
{
    //no change
}

usage:

SingletonTwo.ExistsInSceneOrError();

You're right! It works! Thanks! I actually don't need it to work on System.Type but on a generic class (even if I have to use it on MonoBehaviour only). Would that change the calling syntax to avoid that ugly typeof? What would you change to make it work on a generic class or Monobehaviour? Would it still be possible?

I could not find the solution yet. I have updated my visual studio in hope that this will resolve the problem but there is no solution, this is still happening. I don't have this problem in VsCode or mono, so I think that the problem is related to visual studio or the integration I don't know :(

You can use Extension Methods just fine. its definitely possible.

public interface ISingleton{} // marker interface
public static class SingletonExtensions
{
    private static Dictionary<System.Type,UnityEngine.Object> singletons = new Dictionary<System.Type, UnityEngine.Object>();

    public  static bool SingletonExists<T>(this Component component)
        where T:UnityEngine.Object
    {
        return GetSingleton<T>()!=null;
    }
    public  static T GetSingleton<T>(this MonoBehaviour script)
        where T:UnityEngine.Object
    {
        T singleton = GetSingleton<T>();
        if(singleton == null)
        {
            Debug.LogErrorFormat(script,"Singleton {0} not found!",typeof(T));
        }
        return singleton;
    }
    public  static T GetSingleton<T>(this ScriptableObject script)
        where T:UnityEngine.Object
    {
        T singleton = GetSingleton<T>();
        if(singleton == null)
        {
            Debug.LogErrorFormat(script,"Singleton {0} not found!",typeof(T));
        }
        return singleton;
    }
    public  static void LoadSingleton<T>(this ISingleton script)
        where T:UnityEngine.Object,ISingleton
    {
        T singleton = GetSingleton<T>();
        if(singleton == null)
        {
            Debug.LogErrorFormat(script,"Singleton {0} not found!",typeof(T));
            return;
        }
        script = singleton;
    }

    private  static T GetSingleton<T>()
        where T:UnityEngine.Object
    {
        UnityEngine.Object singletonObject = null;
        System.Type type = typeof(T);

        singletons.TryGetValue(type, out singletonObject);

        if(singletonObject != null) return (T) singletonObject;

        T singleton = UnityEngine.Object.FindObjectOfType<T>();

        if(singleton == null)  return null;

        singletons[type] = singleton;
        return singleton;
    }
}

You can also have it reference the singleton itself as the caller. a little known fact about extention methods is that the instance running the extension can be null so something like this

ISingleton gameController = null;
gameController.LoadSingleton<GameController>();

can work even if gameController was null when the extension method was called.

the question of “Should you use Extension methods for this?” however is debatable.

I definitely agree I should be using other ways to accomplish this (and I actually am using a good compromise), but I thought it would still be an interesting subject to have an answer for :) Or at least brainstorm a bit around it. I think corner cases are always interesting to explore.

I updated Unity to to version 2017.1.1f1 and the problem still exist

Your code is good. Try use like this:

public static class FNSUtils {
	public static bool ExistsInSceneOrError(this System.Type type) {
		if (GameObject.FindObjectOfType(type) != null)
			return true;
		else {
			Debug.LogError("Couldn't find a " + type.ToString() + " in the scene. Add it, save the scene and reload it");
			return false;
		}
	}
}

And use:

typeof(AnyClassName).ExistsInSceneOrError();

Uhm, you just copied the original code into your answer. How does that answer the question? If you just wanted to show how to use it, you're 3 days late. Glurth already showed that.