operator - types of data conversion in c# mcq

Interfaces, Inheritance, Implicit operators and type conversions, why is it this way? (4)

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)

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.

... 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

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)

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

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 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:

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