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 ()
    {
    }
}
Marek Safar
  • 600
  • 3
  • 8
  • 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) – Jason Larke Sep 26 '12 at 08:57
  • 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. – Hans Passant Sep 26 '12 at 11:56

2 Answers2

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

Lucero
  • 59,176
  • 9
  • 122
  • 152
  • Right, the CLS is not written from a C# perspective. The fact that the parameters have the same type in C# isn't relevant. –  Sep 26 '12 at 09:21
  • `T&` is how such type would be called in CIL too. – svick Sep 26 '12 at 09:23
  • @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. – Lucero Sep 26 '12 at 09:23
  • 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)." – Marek Safar Sep 26 '12 at 09:31
  • @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](http://msdn.microsoft.com/en-us/library/8k2kbc25%28v=vs.71%29.aspx) 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). –  Sep 26 '12 at 09:50
  • hvd: The explanation is mixing CLS/CTS/CIL altogether. f(int) and f(ref int) is NOT CLS-compilant which is correctly detected. – Marek Safar Sep 26 '12 at 10:15
  • 1
    @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? –  Sep 26 '12 at 11:16
  • @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. – Marek Safar Sep 27 '12 at 15:29
  • @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." –  Sep 27 '12 at 17:49
  • @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. – Lucero Sep 27 '12 at 21:44
  • @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. – Marek Safar Sep 28 '12 at 07:23
  • @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? – supercat Nov 14 '12 at 23:04
  • @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? – Lucero Nov 15 '12 at 11:37
  • @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... – supercat Nov 15 '12 at 15:49
  • ...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... – supercat Nov 15 '12 at 15:53
  • ...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). – supercat Nov 15 '12 at 15:57
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...

Community
  • 1
  • 1
glopes
  • 4,038
  • 3
  • 26
  • 29
  • 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. – supercat Jan 22 '15 at 16:58