Chaining implicit operators in generic c# classes
Asked Answered
M

4

9

For the following generic c# class, I'd like to convert T to K:

public abstract class ValueType<T,K> : IValueType<T> where K : ValueType<T,K>,new()
{     
    public abstract T Value { get; set; }     

    public static implicit operator ValueType<T,K>(T val) 
    {         
        K k = new K();
        k.Value = val;
        return k;    
    }
}

If I were to implement a direct operator implicit operator K(T val) it would result in a compile-time error (CS0556).

I thought I could try chaining implicit operators:

public static implicit operator K(ValueType<T,K> vt){
    return (K)val;
}

but the following example still complains that it can't be converted:

public class Test : ValueType<int, Test>
{
    public override int Value{ get; set; }
}

Test t = 6; //complains it's unable to be converted
Console.WriteLine(t.Value);

I really want to avoid explicitly casting if possible.

This question extends upon another SO question I've previously raised.

Moving answered 14/5, 2011 at 12:33 Comment(2)
I don't think that the compiler does apply implicit chaining to casts/conversion operators at all... so I don't see the point here. Or am I missing something?Jacobjacoba
@Jacobjacoba I was wondering if it is possible to chain casts/conversions at all. And if not, is there a workaround?Moving
D
18

The rules for implementing your own implicit conversion logic are quite strict and you should probably become very familiar with sections 6.4.4 (Conversions:User-defined implicit conversions, for C# 8 section 10.2) and 10.10.3 (Classes:Conversion operators, for C# 8 section 15.10.4) of the specification if you're going to do particularly complicated ones like this.

Briefly, a few key rules you should know are:

  • The type that you are defining the conversion in has to appear in either the "to" or "from" portion of the user-defined conversion. You can't make a class C that defines a conversion from E to F; C has got to be in there somewhere.
  • It is never possible to replace a built-in implicit conversion with one of your own; you can't make special behaviour happen when converting from C to object, for example.
  • User-defined implicit conversions will be "chained" with up to two built-in implicit conversions, but not with any other user-defined conversions. So for example, if you have a user-defined implicit conversion from C to D, and a built-in implicit conversion from D to IFoo, then you get an implicit conversion from C to IFoo. But if D has a user-defined implicit conversion to E, then you don't get an implicit conversion from C to E for free.
Duodecimal answered 14/5, 2011 at 14:49 Comment(8)
It seems like this should work for lamba -> Func -> MyClass<T> if an implicit conversion from Func -> MyClass<T> is defined, but the compiler doesn't actually seem to chain the conversions.Shive
@NetMage: Can you justify your claim that it "should work" with a quotation from the specification? This area of the specification is subtle; read it carefully.Duodecimal
@NetMage: Alternatively, think about it from the perspective of the compiler developer. You have a lambda on the right side of an assignment, and a variable of type MyClass<T> on the left. MyClass<T> has arbitrarily many conversions from arbitrarily many delegate types. Describe how you think type inference should derive the formal parameter types of the lambda from this information. I think if you do that you will realize why I didn't do it that way.Duodecimal
I think I found my mistake, which is that only standard implicit conversions are chained with user-defined conversions, and the list of standard implicit conversions (6.3.1) does not include Anonymous function conversions (6.5). Per your question, couldn't the existing Anonymous function conversions provide the set of compatible delegate types that can be used to create the set of possible user-defined conversions, and then the unique rule apply?Shive
@NetMage: Correct; anonymous function conversions are not "standard". Now, you might well say, let's fix that. We'll just treat it as any other overload resolution problem; the lambda-to-delegate conversions are used to determine first applicability of the operator, and then betterness. The source expression needs to be treated as having no type, which complicates the specificity rules somewhat, as they are written in terms of expressions which have types.Duodecimal
@NetMage: All this is doable, given willingness to spend valuable time and effort on designing the feature, modifying the specification, implementing it, testing the implementation, writing the documentation, and shipping it. But you're the first person to suggest to me that this work might be a better use of effort than any other feature that the team could be spending time on; which C# feature would you like to have been cut to fit this in the schedule?Duodecimal
Well, I could unfairly say most of C# 7.0 (tuples?) but you have to go by the majority. However, I stumbled on the issue from a few different (old) Stack Overflow questions around the same issue. It just bugs my language orthogonality OCD. To quote a famous HP-48 (firmware) programmer, "Life is short and ROM is full."Shive
Having seen this come up again in a StackOverflow question, what type would a lambda source expression be in any case? Isn't it already the case for delegate type implicit conversion that it is handled?Shive
J
2

Casts are not chained by the compiler, so that way of solving the issue doesn't work.

Implicit casts are quite strict in the type checking. Your very first snippet and the Test class do work if the compiler knows the type:

ValueType<int, Test> t = 6;
Console.WriteLine(t.Value);

The problem is that your ValueType<int, Test> - from the type system point of view - is not always a Test, so that the implicit conversion doesn't apply there.

Eric Lippert wrote a blog post on this kind of generic self-referencing by the way - worth a read!

Jacobjacoba answered 14/5, 2011 at 12:48 Comment(0)
A
0

As far as I know, I don't think that you can chain casts together, sorry about that.

I've been studying how to create parsers, and if this was possible, there would have to be an indefinite loop to find the connection from T to K. I'm not sure that the C# parser would try doing that, but my money is on no, unfortunately!

Allochthonous answered 14/5, 2011 at 12:44 Comment(6)
If you look at the type system as a graph of types, it's easy to avoid an infinite loop on a cycle by not re-visiting any type (node) you've already visited previsouly in this attempt to resolve the type. So that's not the reason... ;)Jacobjacoba
Interesting Lucero, in your opinion would you do that be done using a visitor pattern or keeping track of which types you've visited in a map of some kind?Allochthonous
I don't like or use the visitor pattern because it breaks the fundamental open/closed principle. That said, the easiest way is to pass along a set of the visited nodes as parameter and to check against it, or to do a breadth-first search approach where you add the nodes pending to be processed to a queue and discard them if they are already present in the set when you dequeue them.Jacobjacoba
The only expierence I had with JavaCC and it's inbuilt visitor support, and I wasn't too keen on it either! I agree with your second idea, and it makes it easier to discard changes as well, as with the visitor pattern you'd have to go back and change the bool that you set back!Allochthonous
Basically it boils down to seeing things as object graphs and not just trees. Trees never have loops, graphs usually have, and therefore looking at algorithms to deal with graphs is the way to go in such situations. Btw, if you ever want to do some parsing in .NET, you may want to look at this - (oh and the disclaimer, I'm the author of the perser engine used in that article, so I do have some bias towards this parser engine ;) ).Jacobjacoba
I'll take a read through that link, I'm fascinated by parsers, and am creating my own in java at the moment (i'm having to deal with UTF-8 files, and none of the parser generators I found could deal with unicode groups properly (JavaCC, Antlr, YACC, LEMON)). How long did it take you to create the parser engine in all?Allochthonous
P
0

Here is what I came up to. Not an answer to the OP question, but as far as I look for, from C# rules it is not possible anyway. So what I did it to implement the implicit operator in concrete class that rely on the conversion algorithm defined in the abstract class.

My classes :

public interface IInjectable<T>
{
    T Value { get; set; }
}

internal abstract class Injectable<T,P> : IInjectable<T>
    where P : Injectable<T,P>, new()
{
    public abstract T Value { get; set; }

    public static implicit operator T(Injectable<T,P> injectable) => injectable.Value;
    
    //public static implicit operator Injectable<T,P>(T value) => new P { Value = value };
    public static P Convert(T value) => new P { Value = value };        
}

internal class InjectableGuid : Injectable<Guid, InjectableGuid>
{
    public override Guid Value { get; set; } = Guid.Empty;

    public override string ToString() => Value.ToString();        

    public static implicit operator InjectableGuid(Guid guid) => Convert(guid);        
}

Usage :

Guid id = new InjectableGuid();
Console.WriteLine(id.ToString());

Guid newId = Guid.NewGuid();
Console.WriteLine("Guid.ToString() : "+newId.ToString());
InjectableGuid injected = newId;
Console.WriteLine("InjectableGuid.ToString() : "+injected.ToString());
Pet answered 6/12, 2022 at 2:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.