4

Follow-up of Is my usage of explicit casting operator reasonable or a bad hack?

One of the users commenting on that question said that it's really bad if the casting operator creates a new object under the hood, as it ceases to be an actual cast. It is of course very valid point.

When one would use the ability of defining custom type conversion? Especially: between two types that are not related by inheritance in any way.

With related objects it makes (some) sense:

class Animal {
    public string Name {get;set;}
    public int Age {get;set;}
}

class Dog : Animal {
    public void Bark(){}
    public void Fetch(){}
}

class Cat : Animal {
    public void Meow(){}
    public void Sleep(){}
}

Although normally casting Cat to Dog wouldn't be possible, it makes sense to explicitly allow it - they share all their data, just the behaviour is different.

Even when there would be some difference in fields:

class Parrot : Animal {
    public int WingSpan {get;set;}

    public void Screech(){}
}

casting still might be desired (Parrot to Cat for example, as Cat doesn't have MORE data than Parrot).

But what about two types that are not related? It is possible to do:

class Rock{
    public int Age {get;set;}
}

and

public static explicit operator Rock(Cat cat){
    return new Rock{Age = cat.Age};
}

but why? As pointed out in related question it would cause quite strange behaviour:

var cat = new Cat{Name = "Fluffy", Age = 8};
var rock = (Rock) cat;
rock.Age = 100000000;
Console.WriteLine(rock.Age); // 100000000
Console.WriteLine(cat.Age); // 8

Can anyone give a good example of when implementing explicit operator between unrelated types makes sense and how to do it?

2 Answers 2

9

Can anyone give a good example of when implementing explicit operator between unrelated types makes sense and how to do it?

I will argue that it never makes sense to overload the explicit conversion operator. That operator has a well defined behavior, and using it for a different behavior will always fall into the realm of "clever". Just use a function.

That said, I've used the implicit conversion operator a few times, and I think that it can be used in a net-positive way.

For example, I have a small programming language I'm working on. It has an object to represent an Identifier token in the syntax tree, which is just a wrapper for a string to give it a concrete type (like this recent question asked about). So instead of writing new Identifier("blah") or if(identifer == new Identifier("class")) everywhere, I have an implicit conversion from string to Identifier. It simplifies the code while keeping the behavior clear.

Yes, it does create a new object during conversion. Yes, it requires me to make sure that Identifier has value-style equality and be sure that my class design won't cause horrible problems. But it's is one of those fairly dangerous tools you can use in a few situations to make things a lot more elegant if you know what you're doing.

Explicit conversion overrides have no such upside. They add verbosity to code anyways, so you're always better off using more conventional approaches.

3
  • 2
    IMHO, the explicit conversion operator is appropriate in the particular situation where the operation will either throw an exception or yield something that can be cast back to the original type to yield the original value. Given double d;, the expression (Int32)x should IMHO have been defined to convert d to an integer when it's a finite value with a fractional part of zero, and throw an exception otherwise; other kinds of integer conversion should have used methods RoundToInt32, FloorToInt32, or TruncToInt32, [and likewise for Int64].
    – supercat
    Commented May 5, 2015 at 18:54
  • I would say your case about Identifier is actually very opiniated if you'd like to use implicit or explicit there. Implicit might hide some problems i.e. when you reorder parameters and now your Name string is in your Identifier string. So you lose quite a part of static typing. Making the cast explicit you would have still full static typing with slightly less visual noise. DoSomething( new Identifier(value) ) vs DoSomething( (Identifier)value )
    – Dirk Boer
    Commented Sep 7, 2023 at 16:47
  • @DirkBoer - I’m not following. You have full static typing in either case. With implicit conversion operators it is simply DoSomething(value) rather than either of the noisy conversions. Yes, implicit might hide some problems (which is why it is rarely used), but the explicit gains you nothing over simply calling the ctor.
    – Telastyn
    Commented Sep 7, 2023 at 23:41
4

The casting operator is an operator, and like all operators you shouldn't overload it unless that overloading preserves the accepted meaning of the operator in the environment you work in. One of the reasons Scala confuses so many people is that it's libraries tend to hijack operators for DSL creation, making it very hard to understand the library and write code that uses it just from reading the API. Don't do that.

The same operators might have different accepted meanings in different languages - e.g. << and >> usually mean shift, but in C++ they also mean send-to/read-from stream. The accepted meaning can also come from the domain - for example many libraries that deal with paths like to use the division operator / for joining paths - because that's what used in the shell.

But the cast operator has the same general meaning everywhere - when I cast type X to type Y, I'm saying "a Y is essentially a X, only ...". The part after the only can make a world of difference - but I'd argue that any overload of cast that can't be translated to this template is an abuse of the case operator.

  • (string) 10 - a string is essentially a integer, only represented as text.
  • (int) 5.4 - an int is essentially a float, only without the fraction
  • (string) ['a', 'b', 'c'] - a string is essentially a collection of characters, only concatenated to each other.
  • (Rock) cat - a cat is essentially a rock, only... well...

It doesn't work in the last example - because a cat is not a rock, not matter how you put it. You are getting a weird behavior because what you are trying to do is weird. Just because both rock and cat have an age doesn't mean that a cat is essentially a rock with the same age - that kind of thinking is worse than Weak Typing - not only are you breaking the abstraction that strong typing provides to you that says that 1069547520 is different than 1.5 even though they are have the same representation in the memory - you are creating brand new conversions that make no sense neither in the high abstraction level nor in the low memory level.

So, to your question("Can anyone give a good example...") - the answer is no. Noone can give a good example, because there aren't any good example. Casting doesn't make sense when X and Y are unrelated, because the only cases where a cast operator makes sense is then you can say "a X is essentially a Y, only ...", and if you can say this - that's the relation between X and Y.

4
  • 1
    a string is essentially a integer, only represented as text. That's stretching things quite a bit. I don't see how you can argue that it's sensible to turn a number into a non-number thing and then turn around and say it's not sensible to convert a cat into a rock.
    – Doval
    Commented May 5, 2015 at 16:49
  • Casting was always controversial when it comes to strong typing(I personally think it even implicit casting have a place in strong typed systems, as long as the cast's definition is based on a conscious decision of how X can be turned into Y, and not on coincidental representation of these types in memory), but it makes a few orders of magnitude more sense to claim that the string "10" is related to the number 10 than to claim that a 5-kilogram rock is related to a 5-kilogram cat.
    – Idan Arye
    Commented May 5, 2015 at 17:03
  • A cat is essentially a rock. Both are pets. Commented Aug 24, 2015 at 23:15
  • @ArturoTorresSánchez And with boxing, you can treat everything as an object, and since C# has a single-rooted object hierarchy every two types will have a common ancestor you can upcast to. That's not the point. You can treat a cat as a pet, and you can treat a rock as a pet, but there is no (obvious) meaningful way to treat a cat as a rock. There is, however, a meaningful way to treat a string as a number.
    – Idan Arye
    Commented Aug 25, 2015 at 8:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.