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)

2
  • 1
    a List<DbRecurPatt> is an actual object. You can't cast it to a List<IRecurrencePattern> because someone might then try to stuff a non-DbRecurPatt into it. Commented Aug 26, 2015 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. Commented Aug 26, 2015 at 13:08

5 Answers 5

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

Sign up to request clarification or add additional context in comments.

5 Comments

I think you left the I off the interface name in the implicit operator code example.
The last part of that statement; Did you mean return new IRecurrencePattern() instead of return new RecurrencePattern because upon trying that code, it works?
@AlecScratch No, I believe he meant for the signature to be public static implicit operator IRecurrencePattern(DbRecurPatt obj)
Ah ok, I see that now. My wouldn't make sense anyway because you can instantiate an interface. Derp.
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.
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.

Comments

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

5 Comments

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"?
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.
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: 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 ("Conversions declared as explicit require a cast to be called."), here ("implicit conversion -- no cast needed"), or here
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.
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.

1 Comment

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

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.