22

I'm working with a class library called DDay ICal. It is a C# wrapper for the iCalendar System implemented in Outlook Calendars, and many many many more systems. My question is derived from some work I was doing with this system.

There are 3 objects in question here

  • IRecurrencePattern - Interface
  • RecurrencePattern - Implementation of IRecurrencePattern Interface
  • DbRecurPatt - Custom Class that has an implicit type operator

IRecurrencePattern: Not all code is shown

public interface IRecurrencePattern
{
    string Data { get; set; }
}

RecurrencePattern: Not all code is shown

public class RecurrencePattern : IRecurrencePattern
{
    public string Data { get; set; }
}

DbRecurPatt: Not all code is shown

public class DbRecurPatt
{
    public string Name { get; set; }
    public string Description { get; set; }

    public static implicit operator RecurrencePattern(DbRecurPatt obj)
    {
        return new RecurrencePattern() { Data = $"{Name} - {Description}" };
    }
}

The confusing part: Through out DDay.ICal system they are using ILists to contain a collection of Recurrence patterns for each event in the calendar, the custom class is used to fetch information from a database and then it is cast to the Recurrence Pattern through the implicit type conversion operator.

But in the code, I noticed it kept crashing when converting to the List<IRecurrencePattern> from a List<DbRecurPatt> I realized that I needed to convert to RecurrencePattern, then Convert to IRecurrencePattern (as there are other classes that implement IRecurrencePattern differently that are also included in the collection

var unsorted = new List<DbRecurPatt>{ new DbRecurPatt(), new DbRecurPatt() };
var sorted = unsorted.Select(t => (IRecurrencePattern)t);

The above code does not work, it throws an error on IRecurrencePattern.

var sorted = unsorted.Select(t => (IRecurrencePattern)(RecurrencePattern)t);

This does work tho, so the question I have is; Why does the first one not work? (And is there a way to improve this method?)

I believe it might be because the implicit operator is on the RecurrencePattern object and not the interface, is this correct? (I'm new to interfaces and implicit operators)

D Stanley
  • 149,601
  • 11
  • 178
  • 240
Alec Scratch
  • 277
  • 2
  • 10
  • 1
    a `List` is an actual object. You can't cast it to a `List` because someone might then try to stuff a non-`DbRecurPatt` into it. – Damien_The_Unbeliever Aug 26 '15 at 12:52
  • It should be noted that a cast from `RecurrencePattern` to `IRecurrencePattern` will refer to the same object, but a cast from `DbRecurPatt` to `RecurrencePattern` creates a completely new object. So, you need to tell it to create the new object before referring to it as an interface. – juharr Aug 26 '15 at 13:08

5 Answers5

16

You have basically asked the compiler to do this:

  1. I have this: DbRecurPatt
  2. I want this: IRecurrencePattern
  3. Please figure out a way to get from point 1. to point 2.

The compiler, even though it may only have one choice, does not allow you to do this. The cast operator specifically says that DbRecurPatt can be converted to a RecurrencePattern, not to a IRecurrencePattern.

The compiler only checks if one of the two types involved specifies a rule on how to convert from one to the other, it does not allow intermediary steps.

Since no operator has been defined that allows DbRecurPatt to be converted directly to IRecurrencePattern, the compiler will compile this as a hard cast, reinterpreting the reference as a reference through an interface, which will fail at runtime.

So, the next question would be this: How can I then do this? And the answer is you can't.

The compiler does not allow you to define a user-defined conversion operator to or from an interface. A different question here on Stack Overflow has more information.

If you try to define such an operator:

public static implicit operator IRecurrencePattern(DbRecurPatt obj)
{
    return new RecurrencePattern() { Data = $"{obj.Name} - {obj.Description}" };
}

The compiler will say this:

CS0552
'DbRecurPatt.implicit operator IRecurrencePattern(DbRecurPatt)': user-defined conversions to or from an interface are not allowed

Community
  • 1
  • 1
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • 2
    I think you left the `I` off the interface name in the implicit operator code example. – juharr Aug 26 '15 at 13:01
  • The last part of that statement; Did you mean `return new IRecurrencePattern()` instead of `return new RecurrencePattern` because upon trying that code, it works? – Alec Scratch Aug 26 '15 at 13:04
  • @AlecScratch No, I believe he meant for the signature to be `public static implicit operator IRecurrencePattern(DbRecurPatt obj)` – juharr Aug 26 '15 at 13:04
  • Ah ok, I see that now. My wouldn't make sense anyway because you can instantiate an interface. Derp. – Alec Scratch Aug 26 '15 at 13:05
  • Yes, I was missing an I in the signature, I copied the wrong method from my example LINQPad program, but it is correct that I wanted to use the class inside the method. – Lasse V. Karlsen Aug 26 '15 at 13:16
6

Why does the first one not work?

Because you're asking the runtime for two implicit conversions - one to RecurrencePattern and one to IRecurrencePattern. The runtime will only look for a direct implicit relationship - it will not scan all possible routes to get you ask it to go. Suppose there were multiple implicit conversions to different types of classes that implement IRecurrencePattern. Which one would the runtime choose? Instead it forces you to specify individual casts.

This is documented in Section 6.4.3 of the C# Language specification:

Evaluation of a user-defined conversion never involves more than one user-defined or lifted conversion operator. In other words, a conversion from type S to type T will never first execute a user-defined conversion from S to X and then execute a user-defined conversion from X to T.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
4

As others have pointed out already, you can't make a direct jump from DbRecurPatt to IRecurrencePattern. This is why you end up with this very ugly double cast:

var sorted = unsorted.Select(t => (IRecurrencePattern)(RecurrencePattern)t);

But, for completeness' sake, it should be mentioned that it is possible to go from a DbRecurPatt to a IRecurrencePattern without any casts with your current design. It's just that to do so, you need to split your expression into multiple statements, and by doing so, the code does become considerably uglier.

Still, it's good to know that you can do this without any casts:

var sorted = unsorted.Select( t => {
    RecurrencePattern recurrencePattern = t; // no cast
    IRecurrencePattern recurrencePatternInterface = recurrencePattern; // no cast here either
    return recurrencePatternInterface;
});

EDIT

Credit to Bill Nadeau's answer for the idea. You can also benefit from implicit conversion and its compile time guarantees while keeping the code pretty elegant by writing it this way instead:

var sorted = unsorted
    .Select<DbRecurPatt, RecurrencePattern>(t => t) // implicit conversion - no cast
    .Select<RecurrencePattern, IRecurrencePattern>(t => t); // implicit conversion - no cast
sstan
  • 35,425
  • 6
  • 48
  • 66
  • Thanks for the response, one question though, do you know if this is any better than the cast method? Or is this purely a "good to know"? – Alec Scratch Aug 26 '15 at 13:23
  • There is one advantage (which I personally think is a big one): if you can compile code without any casts, then you can feel safe that the conversion won't suddenly fail at runtime. Contrast that with your statement `(IRecurrencePattern)t`, which did compile, but failed at runtime. But there is no denying that the code is uglier and more verbose. – sstan Aug 26 '15 at 13:31
  • There are still two casts there, they are just implicit casts since you're copying the reference from a variable of one type to a variable of another type. So to say there are "no casts" is not correct. – D Stanley Aug 26 '15 at 21:59
  • @D Stanley: I see what you are saying. But I think there is a difference between a *cast* and a *conversion*. Had I said that no *conversion* was necessary, I would agree with you that it would be wrong. For examples where the terminology is used this way in standard docs, you can see [here](https://msdn.microsoft.com/en-us/library/09479473.aspx) (*"Conversions declared as explicit require a cast to be called."*), [here](https://msdn.microsoft.com/en-us/library/85w54y0a.aspx) (*"implicit conversion -- no cast needed"*), or [here](https://msdn.microsoft.com/en-us/library/ms173105.aspx) – sstan Aug 26 '15 at 23:35
  • 2
    Hooray! People like my idea! I think for OP's situation a combination of the two techniques will end up being best and most elegant. In all honesty as long as hard casts are avoided OP should be good. – Bill Nadeau Aug 27 '15 at 20:01
2

There's another path to accomplish what you want. Specifically mark your generic arguments on your method calls instead of letting the compiler infer your generic arguments. You will still avoid casting, and it may be a little less verbose than some of the other options. The only caveat is you must include an additional Linq statement, which will resolve your list, if that matters.

var sorted = unsorted
   .Select<DbRecurPatt, RecurrencePattern>(t => t)
   .ToList<IRecurrencePattern>();

You could also combine this answer with sstan's to avoid the extra Linq statement.

Bill Nadeau
  • 702
  • 1
  • 9
  • 15
  • 1
    I like your idea a lot. I borrowed your idea and made an adjustment to my answer. Basically, I would chain 2 `Select` method calls to avoid casting while keeping the code equivalent to OP's original code snippet. The result is pretty clean. – sstan Aug 27 '15 at 00:04
1

... and to answer your final question about the implicit operator - no, you can't define an implicit operator on an interface. That topic is covered in more detail in this question:

implicit operator using interfaces

Community
  • 1
  • 1
olitee
  • 1,683
  • 10
  • 12