Access protected const from base class in attribute
Environment: I am using .Net Framework 4.6.1 together with the target F# runtime 4.1 (FSharp.Core, 4.4.1.0) in a F# class library which references XUnit (2.2.0, obtained via NuGet) in Visual Studio 2017 Community.
I have a C# base class with a constant
public class TestClassBase
{
protected const string Foo = "Bar";
}
which I can consume in C# as follows
[Trait(Foo, "")]
public class MyCSharpTestClass : TestClassBase
{
}
How do I consume it in a F# class? Foo is not recognized by the compiler.
[<Trait(Foo, "")>]
type MyFSharpTestClass() =
inherit TestClassBase()
The error I get is: FS0267 This is not a valid constant expression or custom attribute value.
[<Trait(Foo, "")>] yields: FS0039 The value or constructor 'Foo' is not defined. Maybe you want one of the following: floor.
[<Trait(this.Foo, "")>] yields FS0039 The value, namespace, type or module 'this' is not defined. Maybe you want one of the following: ThisAssembly.
[<Trait(base.Foo, "")>] yields FS0039 The value, namespace, type or module 'base' is not defined. Maybe you want one of the following: Base.
[<Trait(TestClassBase.Foo, "")>] yields FS0039 The field, constructor or member 'Foo' is not defined.
@gh3x78E I think you need to change it to typeof<Foo>.
[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
inherit TestClassBase()
fails with
error FS1097: The struct or class field 'Foo' is not accessible from this code location
I guess the F# compiler sees the Attribute declaration as outside of the class, so that the access is not allowed.
Compare with
//[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
inherit TestClassBase()
let foo = TestClassBase.Foo
which compiles ok, and shows that accessing the protected member inside the class works.
@NatElkins: Sorry, that does not work - and makes no sense.
@0x53A I do not understand why the behaviour is different in C# and F#. In my opinion there should not be any difference in usage.
@gh3x78E Oops. Sorry, skimmed your original post and thought you might've been trying to pass in a type rather than a string. In the case that your attribute was taking a type (not a string) and Foo was a type (rather than a field), it would've caused an error because in that case Foo would be viewed as a function that returned an instance of Foo.
@gh3x78E This looks like a bug - thanks for the report.
As workaround please simply don't use "protected" in this case on your C# base class. In general, F# de-emphasizes the use of subclassing, and hence "protected" is seen as awkward for F# consumers, and can hit some corner-case issues like this.
A different example of this issue appears to be in Microsoft Coyote (actor library written in C#). There the base class Actor contains a protected attribute to be used by inherited types. This works in C# but not in F#. The reasoning is to prevent scope pollution for C# users, since only Actor subclasses have access the the attributes defined in the Actor parent class. It would be great if it was somehow possible to override the access protection in client code, since this is no longer tagged as a bug. SO question.
since this is no longer tagged as a bug
This is false. This issue is still tagged as a bug.
[<Trait(TestClassBase.Foo, "")>] type MyFSharpTestClass() = inherit TestClassBase()fails with
error FS1097: The struct or class field 'Foo' is not accessible from this code location
I guess the F# compiler sees the Attribute declaration as outside of the class, so that the access is not allowed.
Compare with
//[<Trait(TestClassBase.Foo, "")>] type MyFSharpTestClass() = inherit TestClassBase() let foo = TestClassBase.Foowhich compiles ok, and shows that accessing the protected member inside the class works.
This no longer works inside functions. If you change to:
//[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
inherit TestClassBase()
let foo () = TestClassBase.Foo
Common if you want to libraries like Akka. This is permitted in C# however
Looking at the actual C# spec for this is, it's unclear to be whether the attribute declaration is part of the program text of the type: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/basic-concepts#declared-accessibility.
The spec is sufficiently vague in that you can read it either way. Though, from the examples above, clearly the implementation of the C# compiler considered it to be part of said program text, while F# does not.
Just saying ;).
That means you can't interop with C# libraries easily.
Bug in C# spec reported: https://github.com/dotnet/csharplang/discussions/3948. Looking a bit deeper into this makes it all rather fuzzy (protected member is disallowed, protected const member is not...), would be nice if some people can write up some definitive rules here that we can follow along in F#.
The answer by @333fred (https://github.com/dotnet/csharplang/discussions/3948#discussioncomment-85803) suggests that this only works "by chance" in the C# compiler. More specifically, the protected const in the OP should not be accessible outside its own class (the attribute constructor call is outside the "program text" of the class) and F# here plays by the C# spec rules (but not the C# compiler).
I'm not saying we shouldn't fix this, but fixing it is basically following a bug (that'll never be fixed due to backwards compat issues) of the C# compiler. I've no idea what the policy is here (cc @dsyme).
Interesting. In that case the C# folks should make an analyzer for it, since it breaks C#/F# interop. It is also interesting that this alleged bug has been used consciously as a feature in the wild (well at least once in a Microsoft library). :-)