17

This is a very uncommon problem and there are definetly many workarounds, but I would like to understand what is actually going on and why it's not working.
So I have 3 assemblies in a test solution, first assembly has type ClassA:

public class ClassA
{
    public string Name { get; set; }
}

Second assembly references first assembly and has ClassB:

public class ClassB
{
    public string Name { get; set; }

    public static explicit operator ClassA(ClassB objB)
    {
        return new ClassA
        {
            Name = objB.Name
        };
    }
}

which has an explicit operator to cast to type ClassA. Let's say that we cannot use inheritance for some reason and just using casting as a convenient way of transforming one type to another.

Now, the last assembly references second assembly (and not the first one!) and has type ClassC:

public class ClassC
{
    public string Name { get; set; }

    public static explicit operator ClassB(ClassC objC)
    {
        return new ClassB
        {
            Name = objC.Name
        };
    }
}

which uses explicit cast operator for same reason as ClassB.

Now the interesting part: if I try to cast from ClassC to ClassB in my code, like this:

ClassC objC = new ClassC();
ClassB objB = (ClassB)objC;

I get the following error:

Error 1 The type 'FirstAssembly.ClassA' is defined in an assembly that is not referenced. You must add a reference to assembly 'FirstAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

I could easily create new instance of ClassB and just initialize it with values from ClassC instance (like I do inside explicit cast operator), and it would work fine. So what is wrong here?

rene
  • 41,474
  • 78
  • 114
  • 152
Ilya Luzyanin
  • 7,910
  • 4
  • 29
  • 49
  • If you do it yourself you know what to do; but if you use the cast you tell the compiler to treat the object by the rules of the cast, which need to know about ClassA and so.. – TaW Apr 10 '14 at 11:20
  • But why? I'm not casting to ClassA anywhere. Is there some source document where these rules of the cast are explained in details? – Ilya Luzyanin Apr 10 '14 at 11:25
  • Interesting question! I tested it on my own and was expecting that *creating an instance of `ClassB`* would fail, because the runtime cannot evaluate the explicit cast member of `ClassB`. Surprisingly it worked! I guess it's somehow related in binding static members. – Carsten Apr 10 '14 at 11:31
  • Perhaps a compiler bug... hopefully Eric Lippert will swipe in soon – Omri Aharon Apr 10 '14 at 11:44
  • :) I was thinking of Jon Skeet. I don't think this is a bug though, I guess a compiler tries to restrict me from doing something, but what - well, I don't know. – Ilya Luzyanin Apr 10 '14 at 11:49
  • @Ilya Luzyanin: You're right. Looks like all or nothing: As long as no cast is used none ist getting included/checked but if one class uses a cast then the checks propagate. Not obvious, until an insider will explain it.. – TaW Apr 10 '14 at 11:50
  • I think simply the issue is that your ClassB exposes ClassA in your explicit to ClassC and therefore the assembly with ClassC wants to know about ClassA. – Allan S. Hansen Apr 10 '14 at 12:14
  • @AllanS.Hansen Why should the explicit operator expose `ClassA` ? C should be converted to B and that operator function creates a new ClassB – Omri Aharon Apr 10 '14 at 12:18
  • 1
    Btw, I added reference to AssemblyA and ran VS2013's performance tool. It verified no `ClassA` objects were created in some implicit way. – Omri Aharon Apr 10 '14 at 12:19
  • You're saying this object can be cast to ClassA in Assembly C but you're not telling C what ClassA looks like. – Allan S. Hansen Apr 10 '14 at 12:31
  • "You're saying this object can be cast to ClassA in Assembly C". Where is it saying that ? :) – Omri Aharon Apr 10 '14 at 12:33
  • `public static explicit operator ClassA(ClassB objB)` in ClassB. Assembly C can see this, but it doesn't know ClassA – Allan S. Hansen Apr 10 '14 at 12:38
  • @AllanS.Hansen Then how come `ClassB objB = new ClassB();` compiles? – Omri Aharon Apr 10 '14 at 12:43
  • Because it's not doing any cast operations. If you change explicit to implicit you also do not need to direct reference between A and C from what I can see. `public static implicit operator ClassA(ClassB objB)`. – Allan S. Hansen Apr 10 '14 at 12:52
  • @Allan S. Hansen - changing explicit cast to implicit won't change anything. I think we'll wait for an insider to explain :) – Ilya Luzyanin Apr 10 '14 at 13:33
  • for me, the error is about the cast not working and not assembly not found... which is so misleading – JobaDiniz Mar 24 '20 at 15:04

1 Answers1

2

In section, 6.4.5 User-defined explicit conversions of the C# Language Specification (version 4.0) it reads:

A user-defined explicit conversion from type S to type T is processed as follows:

• Determine the types S0 and T0. If S or T are nullable types, S0 and T0 are their underlying types, otherwise S0 and T0 are equal to S and T respectively.

• Find the set of types, D, from which user-defined conversion operators will be considered. This set consists of S0 (if S0 is a class or struct), the base classes of S0 (if S0 is a class), T0 (if T0 is a class or struct), and the base classes of T0 (if T0 is a class).

It doesn't define how the compiler will "Find the set of types" but I think it searches all relevant classes looking for candidates for the next step:

• Find the set of applicable user-defined and lifted conversion operators, U. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in D that convert from a type encompassing or encompassed by S to a type encompassing or encompassed by T. If U is empty, the conversion is undefined and a compile-time error occurs.

This causes it to attempt to resolve the reference to ClassA.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Andy Jones
  • 584
  • 2
  • 14
  • 1
    It's the architecture: you refer to type, type's metadata is parsed and members are exposed for your usage. Cast operator is one of those members. While parsing the metadata information about ClassA is found, but metadata of ClassA itself is not available, neither in core libraries nor in assemblies you are already referring. – Andrew Apr 10 '14 at 15:33
  • Your answer disappeared for some time, I'm glad it's back now, so I could accept it. – Ilya Luzyanin Apr 10 '14 at 19:10