locked
Interfaces cannot contain operators

    Question

  •  

    What's the rationale for not allowing interfaces to contain operators?  (Do some .NET languages not have operators?)
    Monday, May 12, 2008 11:40 AM

Answers

  • Operators are simply not compatible with .NET interfaces; interface members work via virtual invoke (i.e. vtable, etc). Operators work as static calls (i.e. the compiler resolves them entirely via static analysis; no virtual invoke).

     

    Since interfaces cannot contain implementation, there is no way to express such static concepts via the interface.

     

    You can get around this by have methods such as:

     

    interface IFoo {

      IFoo Add(IFoo value);

    }

     

    Is there something specific you want to do here? I have other tricks up my sleeve....

    Monday, May 12, 2008 11:51 AM

All replies

  • Operators are simply not compatible with .NET interfaces; interface members work via virtual invoke (i.e. vtable, etc). Operators work as static calls (i.e. the compiler resolves them entirely via static analysis; no virtual invoke).

     

    Since interfaces cannot contain implementation, there is no way to express such static concepts via the interface.

     

    You can get around this by have methods such as:

     

    interface IFoo {

      IFoo Add(IFoo value);

    }

     

    Is there something specific you want to do here? I have other tricks up my sleeve....

    Monday, May 12, 2008 11:51 AM
  • Ah.

     

    In fact that's similar to what I have.  But my types are mutable.  So I have something like this:

     

    interface IAdd<T> {

      void Add(T u, T v); // mutate previously existing instance

      // T operator +(T u, T v);  // create new instance

    }

     

    Having the operator in the interface would allow clients to use the + operator.  This would be helpful in some cases.  For example, they could do

     

    x = y + z;

     

    instead of

     

    x = y.ToNew();

    x.Add(z);

     

    Unfortunately, this does not seem possible.  What tricks do you have?

    Monday, May 12, 2008 7:26 PM
  • Don't give up so soon ;-p

     

    As it happens, I've spent a lot of time looking at operators and generics - this might offer a different approach:

    http://www.yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html

    (download via "miscutil" link)

     

    It works with .NET 3.5 (although I do have a less-tested 2.0 version), and uses the static operators that you have defined. Works with any type - inbuilt or custom, reference-type or value-type.

     

    Let me know if it helps...

     

    Monday, May 12, 2008 8:27 PM
  •  

    Hmm.  What's the advantage of this approach as compared to using interfaces?
    Monday, May 12, 2008 9:11 PM
  • In short - it means I don't need to deal with the interfaces at all - which is even more important if you want to be able to use your code with existing types (Int32, Double, Decimal etc) which obviously aren't going to implement your bespoke interface...

     

    The interface you cited looked like it mapped to the operators - so why duplicate what we have already declared (as static operators, etc)? It also means we can easily add a new operator to the central class (for instance, I added bitwise operations long after the first draft), without having to change all the existing implementations, or add a second interface.

     

    It also avoids having to write lots of constraints when using generics... generic constraints accumulate upwards, so very quickly you have methods with a lot of constraints otherwise.

    Tuesday, May 13, 2008 6:44 AM
  • Well that is interesting.  Using existing types like Int32 and Double wasn't something that I had considered very useful in this project, but now that you mention it, there are generic types where they could be used.   Not having the generic constraints is nice.  I suspect though that this means you're trading compile-time checking for runtime checking, which is not necessarily good.  Still, I would like to know more about what you're doing - I guess it's time to learn about the .NET 3.5 features that you're using.

     

    By the way, the interfaces I'm using don't map to the operators.  That is, the interface methods do not create new instances, whereas the operators do.  (There are a few exceptions; ++ and -- don't create new instances.)    As I said, the types are mutable.  If the interface implementations called the operators, that would force users of the interface to create a mutable instance for each operation performed via the interface.  Avoiding overhead from creating new objects, copying, and initialization is precisely the main reason for having mutable instances instead of making everything immutable.  Can your system work with mutable instances?

    Tuesday, May 13, 2008 7:08 AM
  • Re working with mutable types - well, the operators themselves are not designed to be mutating operations, so if you are talking about standard operators (like your post suggests) then it doesn't need to concern itself with mutability - just that there is a + operator that accepts 2 Foo instances and returns a Foo...

     

    And actually, ++ / -- normally should return new instances; the signature is:

     

    Code Snippet
    public static Foo operator ++ (Foo value) {...}

     

    (and in fact this is enforced: "The return type for ++ or -- operator must be the containing type or derived from the containing type").

     

    Re trading compile for runtime checking; you are correct, but it is no different to calling Sort() without an explicit comparer, etc. In reality, it is unlikely to be a big problem.

     

    Tuesday, May 13, 2008 8:12 AM
  • The point isn't about compatibility with the operators - it's about the ability to handle mutable types.  Can your approach do that?

    Tuesday, May 13, 2008 8:18 AM
  • Well, my approach simply calls the operators you have defined, and returns what the operator returns.

     

    So: are you actually declaring operators here? If so, it'll work fine. If not, you'll need another way of expressing this - in which case the interface approach isn't a bad route, but it is a tradeoff.

     

    But then the question "Interfaces cannot contain operators" becomes moot.

     

     

    Tuesday, May 13, 2008 8:36 AM
  • The mutable types do implement the operators.  Operators other than ++ and -- return new instances.  Mutability's usefulness would be diminished if the only way to get interface-like behavior required using instances as though they were immutable, i.e., via operators.

     

    I'm still curious about your solution.  I happened upon it a few months ago and then, like now, wondered about the details.  Decoding the source without understanding .NET 3.5 seems like it could be painful.

    Tuesday, May 13, 2008 8:52 AM
  • To expand - if you treat the operators as part of a "Fluent API" then it should work fine. An example is below (using a list as the example; for info "AddAlternative" here indicates that the two arguments to "+" are of different types - the first type is assumed to be the return type).

     

    Note that I strongly advise against mutable structs (for countless reasons) - so your approach should be fine as long as you are using classes.

     

    [edit: yes, I know that with a list it would be better to use the IList<T> interface and use the Add/Remove methods directly - this was simply a convenient example of a mutable (and reference) type where I could think of some easy to understand operators]

     

    Code Snippet

    using System;

    using System.Collections.Generic;

    using System.Text;

    using MiscUtil;

    static class Program

    {

    static void Main()

    {

    // test with a list using a "Fluent API"

    MyList<int> list = new MyList<int>();

    AddSubtract(list, 5);

    // test with ints

    AddSubtract(7, 4);

    }

    static void AddSubtract<TSource, TValue>(TSource source, TValue value)

    {

    Console.WriteLine("Initial: {0}", source);

    source = Operator.AddAlternative(source, value);

    Console.WriteLine("After Add: {0}", source);

    source = Operator.SubtractAlternative(source, value);

    Console.WriteLine("After Subtract: {0}", source);

    }

    }

    public sealed class MyList<T> : List<T>

    {

    public static MyList<T> operator +(MyList<T> list, T item)

    {

    list.Add(item);

    return list;

    }

    public static MyList<T> operator -(MyList<T> list, T item)

    {

    list.Remove(item);

    return list;

    }

    // use ToString to describe the list (for output purposes)

    public override string ToString()

    {

    StringBuilder sb = new StringBuilder();

    sb.Append(Count).Append(" item(s)");

    foreach (T item in this)

    {

    sb.Append("; ").Append(item);

    }

    return sb.ToString();

    }

    }

     

     

    Tuesday, May 13, 2008 9:00 AM
  •  Andrew Shapira wrote:
    ...and then, like now, wondered about the details.  Decoding the source without understanding .NET 3.5 seems like it could be painful.

     

    It is a good job I wrote it up, then:

    http://www.yoda.arachsys.com/csharp/genericoperators.html

     

    The real code is a lot more refined, with delegate caching, and a lot of other things that really aren't very interesting - but the fundamental details of how it works are all in the short paragraph titled: "How can we do it, then?"

     

    Tuesday, May 13, 2008 9:09 AM
  •  

    I see.  Instead of using the expression type, one can use reflection to look for methods with prearranged methods and signatures.

     

    What you have here is actually a general technique that could be applied to things other than expression evaluation.  I believe that this generality could even be handled automatically.  Associated with each given named operation X could be a delegate that is used as a template when inspecting a given class T to find the appropriate method for X.

    Tuesday, May 13, 2008 8:17 PM
  • Exactly - and that is how my 2.0 version works; it knows to expect the static op_whatever pattern that standard operators exhibit. It then uses Delegate.CreateDelegate to create a typed delegate to that method.

     

    The problem (as per the doc) is that the intrinsics (Int32, Double, etc) don't *have* op_whatever methods - so I had to add proxy methods for all the intrinsics. It also had to handle Nullable<T> following the rules for "lifted operators" - so not a small task, but it works...

     

    I've got a copy of it somewhere (I only did it to check it was possible; I am happy with the 3.5 version for real code... since that is what I generally use now ;-p)

     

    Tuesday, May 13, 2008 8:23 PM
  •  

    One minor comment: my preference is a model where nothing can be null, and to avoid using null to indicate not present.

     

    http://www.codeproject.com/KB/architecture/option1.aspx

     

    If one is dealing with legacy code or code where extreme performance is desired, than one might have to deal with null; otherwise I try to avoid it now.  I think it might actually be preferable to not incorporate support for Nullable<T> into Operator, because I view Nullable<T> as dangerous when used with arithmetic operators.  To each his own.

    Tuesday, May 13, 2008 8:50 PM
  • Well, a design goal was to allow something as similar to what the language offers as possible. The language offers "lifted operators", so soes does Operator ;-p

     

    But I know what you mean; one usage of Operator (in miscutil) is in an alternative LINQ implementation, where we have provided the usual Sum / Avg / etc - and handling null *efficiently* (in all possible cases, while keeping the code simple) was not fun. The simple "!=null" isn't enough - it can occasionally have some issues in the JIT re boxing.

     

    I can see what you are saying with Option<T>, but IMO it looks like it isn't really simplifying much - just replacing one set of fluff with another...? Or am I missing something...

    Tuesday, May 13, 2008 9:04 PM
  •  

    Option<T> is deceptive because it looks simple.  But it's very useful.  Once you start using Option<T> you might be amazed at how much it helps.  The option pattern is everywhere.

     

    Think of Option<T> in regards to command line argument processing.  Or think of returning an Option<T> instance instead of having a TryXX interface like Int32.TryParse, where part of the data is returned in a return value and part of it is in 'out', and the two pieces of data are coupled but there is no enforcement of this coupling.  Or think of parsing XML data where a field might or might not be present.  And if you want to avoid using null, this is the best way I know, short of language support.

     

    Option<T> really does make things clearer, and reduces the probability of error.  I prefer it to Nullable<T> because it avoids any reference to 'null' and because it does not provide implicit conversions to zero or other defaults.

    Tuesday, May 13, 2008 9:20 PM
  • Option<T> really does make things clearer, and reduces the probability of error.

     

    But I'm not sure it does! The only differences between Option<T> and Nullable<T> are the ": struct" constraint, and language support. In the context of operators, there is no difference between having two Option<T>s (and checking IsPresent and using Value) and having two Nullable<T>s (and checking HasValue and using Value). You'd still need to define what you do if one or both isn't present (IsPresent == false) - and that is exactly what the "lifted operators" rules define.

     

    In fact, in the LINQ code I generally used the longhand (HasValue etc) when dealing with Nullable<T> for clarity, but that is just a language point - it doesn't really impact the bigger picture.

     

    So: if your main point was "Nullable<T> doesn't provide like functionality for reference types [where null is a legal value]", then I'd be in complete agreement that Option<T> resolves this etc - but to my mind that is the only significant difference... The use of null tests vs checking HasValue/IsPresent isn't a biggie... and I think the special boxing rules are a good thing. And I'm not anti-"out", especially in the Try{...} pattern where it is now quite normal (and so I think most developers will be familiar with it in this context).

     

     

    Tuesday, May 13, 2008 9:48 PM
  •  

    There are a lot of reasons why I prefer Option<T> over Nullable<T>.  These are covered in part 2 of the papers in the option series.  I have not had time to polish that and the other articles in the series for publication.  I can give a summary of the discussion of Nullable<T> though.

     

    You already mentioned one major problem with Nullable<T> - it can't be used with reference types.  This is problematic in more contexts than the obvious context of wanting to use Nullable<T> with a reference type.  For example, in a generic type implementation where T is a parameter, one can't use Nullable<T> without restricting T to be a struct.

     

    Second, with Option<T> vs. Nullable<T>, one emphasizes function (option) whereas the other emphases implementation ("null").  Implementation details like how the 'not present' state is represented generally should not be included in a type's name.  In this case, doing so encourages the use of 'null', which is a third disadvantage in my opinion, since null should be discouraged.  (Reasons for avoiding 'null' are discussed more in the article series.  I am not alone in disliking 'null'.)

     

    Fourth, the defaults supplied by Nullable<T> encourage a simple kind of programming error.  The approach I prefer is to allow operations to succeed only when the operands are present.  This is consistent with the 'succeed or fail' contract-based approach that is commonly used in determining when to throw exceptions.  When writing code for a method, if the method finds that preconditions for calling the method are not met, then the method throws an exception.  This is standard in object oriented programming.  Nullable<T> does not follow this approach, and so errors like the following are easy to make:

     

    static void h(int? x)
    {
      if (x >= 0) {
        Console.WriteLine("positive");
      } else {
        Console.WriteLine("negative");
      }
    }

     

    I understand that for database reasons one might want Nullable<T> to behave the way it does in this respect, but as a general programming tool this default behavior is not what I want.

    Tuesday, May 13, 2008 11:32 PM