Search code examples
c#implicit-conversion

Hide implicit operators based on condition?


Probably a dumb question because the answer is likely to be no; but since I can't seem to find a documented answer on the web I'll ask here. I have a class that has six implicit conversion methods. The reason for this is simplification of dynamic logic elsewhere in my application.

// Just an example for simplicity.
public class Number {
    private object val;

    public Number(int i) { val = i; }
    public Number(long l) { val = l; }
    public Number(double d) { val = d; }

    public static implicit operator int(Number n) => (int)n.val;
    public static implicit operator long(Number n) => (long)n.val;
    public static implicit operator double(Number n) => (double)n.val;
}

I am wondering if there is a way to hide the conversions (as in throw compiler error or similar) that won't work based on what the Number class in my example is instantiated with? If there isn't a way to hide it, is there a way to ensure the proper implicit conversion is always used?

I believe a safe route would be just to make val a public property called Value and just return the object and make explicit conversions instead. Also, as a note I am not doing this with basic types; I am trying to encapsulate a group of custom types that are all similar but have no common base class to my misfortune.


Solution

  • In effect, the express purpose of the Number class you've defined is to circumvent the compiler and allow implicit casting which may be invalid. You're telling the compiler not to check the type, and instead allow a runtime exception if the cast isn't valid.

    Trying to get the compiler to provide a warning means trying to get it to do exactly what you told it not to do.

    In a sense, it's no different from just using object instead of Number.

    If you choose to use object then the compiler won't help you. You're giving up type safety, which means that it's up to you to somehow make sure that you keep track of where each value comes from and what it can or can't be converted to so that you don't get runtime errors.

    Type safety is just that - it's safety. It's guard rails that help us not to write code that's going to fail when it runs. I recommend always using it unless there's a really important reason not to, which is usually when the specific type doesn't matter. It usually does.

    Here's another way of looking at it: If the various types to which you want to implicitly convert have no commonality, then there's no benefit to passing an instance to a method that may or may not be able to use it.

    On the other hand, suppose there is a commonality. Say, for example, each of your underlying types has a property that returns a string, but the property is different for each type. In that case you could create an interface, like this:

    public interface IHasMyStringProperty
    {
        string GetTheString { get; }
    }
    

    Then, create a wrapper for the different classes, something like this:

    public class Foo
    {
        public string FooString { get; }
    }
    
    public class FooWrapper : IHasMyStringProperty
    {
        private readonly Foo _foo;
    
        public FooWrapper(Foo foo)
        {
            _foo = foo;
        }
    
        public string GetTheString => _foo.FooString;
    }
    

    Notice that we're not updating the Foo class to implement an interface, although that might be an option too. Instead, we're creating a common interface and creating wrappers (or adapters) to adapt the various types to that common interface. (You could have both - classes that implement the interface and classes that need wrappers.) The result is type safety. We write all of our other methods to work only with IHasMyStringProperty. If we want to pass some other type, we just have to define a wrapper that implements that interface.