Why is overloaded method differing in ref only CLS compliant
Asked Answered
T

2

7

Common Language Specification is quite strict on method overloads.

Methods are allowed to be overloaded only based on the number and types of their parameters, and in the case of generic methods, the number of their generic parameters.

Why is this code CLS compliant (no CS3006 warning) according to csc?

using System;

[assembly: CLSCompliant (true)]

public class Test {
    public static void Expect<T>(T arg)
    {
    }

    public static void Expect<T>(ref T arg)
    {
    }

    public static void Main ()
    {
    }
}
Titmouse answered 26/9, 2012 at 8:54 Comment(2)
Because a reference to a type is not the same as the type itself. For myself I always just look at comparison between T and ref T as the same as T vs T* (even though they are semantically different, it helps me to organize the difference in my C-wired brain)Sanitize
Clearly this is because you use generic methods. Generics are heavily non-compliant but too useful to throw out. The specs are silent about this.Rotifer
P
2

This is CLS-compliant because the types differ. The rules for overloading are requiring one (or more) of the criteria to be met, not all of them at the same time.

A ref T (or out T, which is using the same with same type different semantics) is declaring a "reference" to a T reference (for classes) or the instance (in case of value types).

For more details, look up the Type.MakeByRefType() method - it creates the type representing a reference to the original type, e.g. for a T this returns a T& (in C++ notation).

Phosphatize answered 26/9, 2012 at 9:17 Comment(16)
Right, the CLS is not written from a C# perspective. The fact that the parameters have the same type in C# isn't relevant.Lance
T& is how such type would be called in CIL too.Quincunx
@hvd, exactly, it is actually more a syntax thing; it's because there is no other notation for reference types. The difference between ref and out is only an attribute added to the parameter, which "tells" the C# compiler that the method will always set the value, therefore passing in uninitialized values is OK.Phosphatize
I think you are missing the point of CLS, it knows nothing about ref/out. From MSDN CS3006 description "A method does not cannot be overloaded based on the ref or out parameter and still comply with the Common Language Specification (CLS)."Titmouse
@marek.safar Interesting. The explanation in this answer is correct, at the IL level, f(int) has a different parameter type than f(ref int), so it's strange that the CS3006 sample gets rejected, but it indeed does. What's also strange is that CS3006 does not appear to be in the VS2012 documentation (and perhaps not VS2010/VS2008 either, I haven't checked that).Lance
hvd: The explanation is mixing CLS/CTS/CIL altogether. f(int) and f(ref int) is NOT CLS-compilant which is correctly detected.Titmouse
@marek.safar Why not? The parameters really do have different types. One has type int, the other has type int&, and overloading on different types is allowed. Or are you saying that the function is written in C#, in C# the types are both int, so it's not allowed? Would that mean that the equivalent in CIL, which results in a bitwise identical assembly, is CLS compliant?Lance
@hvd: CIL does not really matter here this is about CLS which is subset of CIL. int and int& overload are not CLS- compliant, similarly Foo () and foo () are not CLS-compliant even if they have different CIL signature.Titmouse
@marek.safar Again, why not? The CLS rules say the types must be different, the types are different if I give one overload a parameter of type int, and another a parameter of type int&. The specification doesn't say "the types must be very different" or anything of the sort, does it? As for Foo() and foo(), they are not allowed because the CLS explicitly says "For two identifiers to be considered distinct, they must differ by more than just their case."Lance
@marek.safar, I have to admit that I'm a bit puzzled by you and your question. If I got it right you are working on the C# Mono compiler and certainly have a good overall knowledge of CIL and CLS. Therefore I'm sure that you know the difference between a type T and its reference T& - they are distinct types in .NET. As such it is not surprising to me (and hvd I think) that those are acceptable as CLS-compliant overloads. In C#, a method with one parameter ref T would not allow for a method identical except for the parameter being an out T, since they use the same type on the IL level.Phosphatize
@Lucero: I am trying to find out why overloads on int/ref int (or any other concrete type) are not compliant and overloads on T/ref T are compliant.Titmouse
@Lucero: How would one go about calling one overload versus the other in a language like vb.net which does not distinguish between ref and non-ref parameters at a call-site?Hosfmann
@supercat, I'm not sure what you mean. Since I'm not using VB.NET I might be missing something obvious, but I though that it had those two keyword ByRef and ByVal for that purpose?Phosphatize
@Lucero: Those keywords are used at the method definition. When making a call like Interlocked.CompareExchange(nextLink, newLink, oldNextLink) the compiler knows that the function is defined as taking its first parameter as ByRef without that being specified at the call site. In many ways I like the design of the vb.net language better than that of C# (e.g. the way it uses a reference-equality operator rather than overloading the value-equality operator) but I can see definite advantages to specifying ref at the call site. Actually, what I'd really like to see...Hosfmann
...would be const ref and value ref parameter types (as well as a means for structure methods to specify that they accept their parameter via either one of those or non const ref, the latter forbidding their use on read-only structure values). A const ref parameter would be a ref which would be run-time enforced as never being written to; a value ref would be likewise, but with the proviso that the compiler would be allowed to make a copy of the parameter if it couldn't confirm that it wouldn't be written to. Many people moan about const-correctness, but it's better than...Hosfmann
...having compilers pass references to copies of things without issuing any diagnostics. If there were a means of easily specifying that a compiler should pass a reference to a copy of something in particular cases where const-correctness could not otherwise be assured, that would be better than having it pass around copies all the time (especially since in many cases passing around a copy would either be redundant or semantically wrong).Hosfmann
H
0

To be clear, in general, overloaded methods differing only in ref or out, or in array rank, are not CLS-compliant, according to MSDN.

You can verify the compiler does indeed check for this specific case by writing a simple non-generic version:

using System;

[assembly: CLSCompliant (true)]

public class Test {
    public static void Expect(int arg)
    {
    }

    public static void Expect(ref int arg)
    {
    }

    public static void Main ()
    {
    }
}

However, you seem to have hit upon a compiler edge-case, since if you add in the generic method overloads, the compiler doesn't seem to complain.

I would say this is either a bug in the compiler (as in this similar question), or there is indeed a more relaxed specification for generics, since they are a latter addition to the specification.

I would err on the side of some kind of compiler limitation, given that this example also raises CS3006:

using System;

[assembly: CLSCompliant(true)]

public class Test<T>
{
    public static void Expect(T arg)
    {
    }

    public static void Expect(ref T arg)
    {
    }

    public static void Main()
    {
    }
}

Apparently adding the generics to the class, rather than the method, raises the compiler's attention...

Hotfoot answered 12/8, 2014 at 8:20 Comment(1)
I would guess such overloading isn't CLS-compliant, and the compiler's failure to flag it is simply a bug. The CLS does not require that languages provide any mechanism for method callers to distinguish between overloads that differ only in whether particular parameters are values or byrefs; if two overloads differ in that but are otherwise alike, at least one will be unusable in a language which can't make such a distinction.Hosfmann

© 2022 - 2024 — McMap. All rights reserved.