If you can not use ExtensionMethods in a static class, how do you extend its functionallity?

I want to extend the function Log of the static class Debug, and add some functionality to it too.

But, so far, the only way I could do it, was by wrapping it in another class:

using System.Collections.Generic;
using UnityEngine;

namespace UnityEngine
{
    public class DebugPro : Debug
    {
       
        /// <summary>
        /// Creates a 'Debug.Log' message with all the contents in the enumerable.
        /// </summary>
        /// <param name="separator">The string that will be in-between each string of each element (the default is ', ').</param>
        /// <param name="message">The message that will be displayed at the beginning of the 'Debug.Log' message.</param>
        /// <param name="context">Object to which the message applies.</param>
        public static void LogEnumerable<T>(IEnumerable<T> enumerableToDebug, string separator = ", ", string message = "", Object context = null)
        {
            enumerableToDebug.DebugLog(separator, message, context);
        }
    }

}

I would really like to keep the name “Debug” in favour of the ease of use.
Any ideas?

You can’t extend it because Debug is sealed, like most engine components:

6913295--810536--Screen Shot 2021-03-08 at 7.27.03 AM.png

Interestingly, it APPEARS that with a little bit of extra typing and an other layer of dereference, you can get what you want by wrapping it with the exact same-named class:

I just made that 2 minutes ago and it works in my Jetpack Kurt game, but there might be other side effects, but those are left for C# namespace gurus to understand and perhaps add commentary here.

1 Like

This isn’t an inheritance issue, but rather a namespace issue.

(note - in this context namespace refers to ANY name in your library. This includes both the ‘namespace’ at the top of your *.cs file, as well as the names of enums/classes/structs/etc. It’s just like interface can refer to both the interface construct, and the actual interface of an object through which you access it.)

Static methods are static… they aren’t necessarily inherited. Rather instead they exist in the scope of a namespace that happens to be in a class. Yes if you inherit from say MonoBehaviour you have direct access to a static method like ‘Destroy’ without having to say UnityEngine.Object.Destroy… but that’s not necessarily because you inherited ‘Destroy’ in the sense of the OOP concept of inheritance. It’s because your namespace overlaps with UnityEngine.Object where ‘Destroy’ is located. The compiler is just being nice to you is all. It’s allowing you to shortcut say ‘Destroy’ instead of forcing you to type out the long form name, similar to how ‘using’ lets you type shorthand namees rather than the long form name.

You are attempting to overwrite/replace a namespace. You can’t do that.

You can create a new namespace. One option is to change the name of your static class like you do in your OP.

Another option is to create a new long namespace for your Debug static class to exist in. Changing the “namespace” it is in to say:

namespace MyCustomNamespace
{
    public static class Debug { }
}

This second option being what @Kurt-Dekker did.

But you can’t have them both as ‘using’ at the same time as you’ll get an ‘ambiguous reference’ compiler error. The compiler doesn’t know which class you’re talking about.

That’s the whole problem here… the namespace is how the compiler figures out what it is you’re referring to. If something already exists with the same namespace (such as UnityEngine.Debug), you can’t replace that. It already exists. And the ‘using’ statements is just there to ease the need to type out the long form name, but if you’re using 2 namespaces that have the same names in it… well this collision of namespace names result in that ambiguous error again. Is it “UnityEngine.Debug” or “KurtDekkersLibrary.Debug”?

Unfortunately this is just the nature of how C# deals with namespaces.

And again… has nothing to do with inheritance.

Also note that ‘ExtensionMethods’ don’t work for static classes for similar reasons. All an ExtensionMethod is, is some syntax sugar. You’re creating a static method that accepts an instance object… then the compiler just allows you to imply the namespace context as long as you’ve applied the name to the end of an object that is the type the extensionmethod expects.

public static class MyExtMethods
{
    public static Vector3 GetPos(this GameObject go) { return go.transform.position; }
}

//elsewhere
var go = GameObject.Find("SomeGO");
var a = MyExtMethods.GetPos(go);
var b = go.GetPos();

Both a and b are calling the same method.
‘a’ is called using the traditional syntax of how a static method is called.
‘b’ is called using syntax sugar that the compiler just unwraps and generates IL that looks/behaves identical to the way ‘a’ was called.

1 Like

An analogy;

Say we have a Robert Smith and a Robert Dent.

If I’m in a room that exists of just me and Robert Dent, I don’t have to keep calling him Robert Dent. I can just call him Robert. Because contextually we’re in a room that only he and I exist in.

If the topic of Robert Smith comes up… I would refer to Robert Smith in the long form by saying “Hey Robert, have you met Robert Smith yet? You share his name.”

But if Robert Smith comes into the room… well now there’s a problem. There are now 2 Roberts in the room. So without some pre-agreed upon name changes, I have to call them both in their long form names. “Hey Robert Dent, meet Robert Smith. He just showed up.”

Now if I expect Robert Dent and Robert Smith to be in the same rooms often… we may decide to call them unique name each. Say Rob D and Rob S. This is similar to the explicit using name:

using UnityDebug = UnityEngine.Debug; //now in the context of this cs file if I say 'UnityDebug.Log'... the compiler reads it as UnityEngine.Debug.Log
using MyDebug = MyLibrary.Debug; //and this allows me to say MyDebug.Log instead of MyLibrary.Debug.Log

Now what you’re trying to do is name a 3rd person Robert Dent as well. Well… now who is who???

This is where the analogy falls apart. Cause in the real world the system does not collapse just because you named someone the same name. We give them a middle name, or just go “that Robert Dent” and point at them. Or some other nonsense to deal with the confusion. Or just let the confusion exist and we all just get confused whenever you bring up Robert Dent and attempt to figure out which one you mean from context.

In computer though… computers ain’t that smart to deal with that situation. If the computer gets confused… it just halts (well in the case of C# the compiler halts… a computer might just take the FIRST Robert Dent it finds). It doesn’t know to say “Which Robert Dent did you mean?”. It just goes “AHHHHHHHH TOO MANY ROBERTS!”. Like someone with autism (like myself) who gets irritated with these weird social norms everyone has that it just can’t comprehend.

3 Likes

I think I agree with everything already said, though I might quibble on some of the details if pressed.

I’ll just add this bit of info which might be useful…

I found the Debug class is defined in the two files here:
UnityCsReference/Runtime/Export/Debug at master ¡ Unity-Technologies/UnityCsReference ¡ GitHub

The relevant detail is that Debug.Log is a static method.

The class isn’t sealed, so it could be inherited. However, if you did that, you would probably want to give the derived class a name other than Debug, or else you would run into the name ambiguity lordofduct goes into. Also, static methods aren’t overridable, so there is very little value in this approach. If your ambitions only include modifying static methods, you can forego the inheriting and simply chain call into the Debug statics.

Extension methods aren’t really applicable because they are a technique for simulating instance methods. They’re not for modifying an existing method (though they can hide existing methods), and not for adding static methods. Since there is already a static method with the name Log, I don’t think you are allowed to add an instance method by that name. Even if you were allowed to do this, it’s not the method you would be hitting when writing code like Debug.Log(), since there is no instance of the Debug class involved here.

Your problem highlights one of the pitfalls of static methods: they are basically impossible to intercept.

If your question is just practical, and you want to begin writing code that makes use of a different behaviour, creating a new Debug class, ideally with a unique name, is your simplest solution. The rest of your code should then call YourNamespace.YourDebug.Log and that will call into UnityEngine.Debug.Log as needed.

If your question is more theoretical, and you just have a distaste for the static nature of Debug.Log, then we could continue the conversation into pattern based alternatives.

1 Like

While this doesn’t solve the problem maybe it helps to mitigate it by providing an alternative.

public static class ExtensionMethods_Misc
{
   public static void Log(this object obj)
   {// writes to content to the standard unity console, shortcut for Debug.Log(obj); use it fe directly on a string
       Debug.Log( obj );
   }
}

use:
int myCount = 7;
string.Format("MyCount is {0}", myCount).Log();

You could constrain the type to your enumerable if you want and put your functionality in there. So you don’t call Debug.Log(enumerableVariable) but enumerableVariable.Log().

Maybe this is of help too.

1 Like

HAHAHAH Thanks for your explanation! It was very helpful

Thanks a lot! With all the information, I think that I will keep it implemented the way I have it now (maybe changing the name to DebugEssentials??).

The question was practical, I am implementing an “essentials” asset and I want it to be integrated seamlessly. I want the user to do not have to “learn” how to use it.
You can check it out here: guplem/UnityEssentials: An asset that that works as a set of tools and features that the average Unity developer would most likely need (github.com)