Question

I have built a custom typesystem for usage in C# scripting inside an application. The scripts are compiled on-the-fly and allow interaction with the application internal data. This typesystem is designed in an abstract way using interfaces like IValue. Implementation of IValue can be a RefString, RefInteger, RefDouble (among many others, but this is sufficient for demonstrating my issue).

Now we come to the point where I get stuck... The use of these IValue objects is somewhat unnatural. It is considered as a good design to always use the interfaces to interact with the objects but there is no possibility to define an implicit conversion or overloading an operator for interfaces. This yields to situations where ugly explicit casting is unavoidable so that the right operator gets used.

Example:

IValue Add(IValue a, IValue b)
{
    //return a+b; // won't work: which operator +() to use?
    return (RefInteger)a + (RefInteger)b;
}

In the case of C# in expressions involving value types, implicit conversions are provided. What is a good way to design such a custom system?

I rewrote the typesystem removing the IValue interface and introducing a RefValue base class. This way already a part of the explicit casts could be eliminated. I implemented some of the operator overloading in this base class but that caused a lot of trouble with the default conversion operators... Beside this the logic to implement in the operator implementation implicates a lot of knowledge about the types in the system. I think this is somehow still the way that one must go but what are the rules to follow, to implement this in a good and safe way?

EDIT: After struggling a while, some of the rules I could find out are:

  • Declare implicit only the conversion operators from base-types (int, double, string, ...)
  • Declare explicit the conversions to base-types (to avoid implicit casts to int!! what happens quite often, but why?)
  • To avoid ambiguous calls the +, -, /, * operators should not be overridden in the base class and in the derived classes. [What is the way to go then? I did the operation overloading in the derived classes, this requires casting on use, which is again ugly...]
Was it helpful?

Solution

If you are supposed to be able to make an Add operation on all IValues, perhaps the interface should include an Add method? Then you could do return a.Add(b);, and push the knowledge of how to perform the operation into each type.

One problem is that as it looks now, you could get calls where a is RefString and b is RefInteger, which is probably not what you want. Generics can help fix that:

T Add<T>(T a, T b) where T : IValue
{
    return a.Add(b);
}

(Of course you would need to add null checking and such as appropriate)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top