Open AnthonyDGreen opened 5 years ago
@AnthonyDGreen Why is the first the example?
Console.Clear()
Console.Write("Enter your name: ")
Dim name = Console.ReadLine()
Console.WriteLine("Hello, " & name)
and not?
Clear()
Write("Enter your name: ")
Dim name = ReadLine()
WriteLine("Hello, " & name)
I have only just finished looking at scenario B, but after seeing the debugger run code in what looks like a documentation file with code snippets, I am completely sold!
Apologies if this is already covered in the expansive notes above, but is there a way for us to get this prototype and try it in Visual Studio today?
@AdamSpeight2008 I suspect that it is simply due to that being a different "feature". I agree with what you are saying from one point of view (mine) since I play a lot with console applications; so having the namespace references already in place to better facilitate this would be great for me. However, what if you are writing applications that are WinForms, WPF or something that we aren't yet aware of... does this defaulting to a particular (additional) set of includes introduce problems that are difficult to overcome in the future? With all of that said, I think you'll find #103 an interesting topic regarding this. Although it's not exactly what you are asking... I think it is approaching the same subject.
Can top-level members reference top-level (non-field) locals?
I think this needs more thought. Given code like this:
Dim startTime = Date.Now
Sub M()
End Sub
Sub P()
End Sub
I would expect startTime
to behave like a global variable/field and be accessible within both M()
and P()
, exactly the way it would be if the code was wrapped into something like this:
Public Module Program
Private startTime As Date = Date.Now
Sub M()
End Sub
Sub P()
End Sub
End Module
In other words when writing code without an enclosing class/module, any use of Dim
outside a method should act as if it declared a global field that can be accessed in all the methods within the file. I think this is how scripting languages work(?).
Having scanned most of the notes above and watched all the videos, all I can say is that this is really really cool. I hope that in the final result, there will be a short list of simple rules that describe what is/isn't allowed when writing VB in low-ceremony mode.
For teaching and simple "scripting" purposes, I am hoping that the rules allow equally low-ceremony transitions from code that is just a bunch of statements, to code that groups those statements into functions for clarity, to code that groups those functions into classes/modules.
Thanks a lot for this @AnthonyDGreen, I imagine it has taken a significant amount of your time and it is much appreciated!
@AdamSpeight2008, @DualBrain,
I just didn't think of it when I wrote it. I originally had another example there but swapped it out at the last minute to be slightly more interesting. But that would work 100% fine already by either adding an Imports System.Console
statement to the top of the code-snippet or adding System.Console
to the project default imports. It's not communicated in the UI in any way but project-level imports have always supported importing a type, you just have to type it in manually. In fact, they also support import aliases. The compiler just parses whatever you wrote as though it came after Imports
and then runs with it :)
That said, if I were writing such a script I would definitely import Console
as suggested!
@ericmutta you can still declare fields that the methods can reference you just have to use Private
instead of Dim
. You could write Private Dim
if you wanted, I guess or even Shared
/Shared Dim
. But there's always the possibility to make a local that's truly local and can't be referenced by the members by just saying Dim
.
As for playing with the bits, everything up to Scenario D is in https://github.com/AnthonyDGreen/roslyn/tree/prototypes/vb-top-level and Scenario E is in https://github.com/AnthonyDGreen/roslyn/tree/prototypes/vb-top-level-json
If you clone my fork, sync to that branch and build and set either Roslyn or "VisualStudioSetup.Next" to the start up project it should launch a new instance of VS with those changes. Sometimes it doesn't deploy the compiler right and so the changes work in the IDE but not on build and to get Scenarios D and E working I had to manually overwrite the compiler in the MSBuild folder because doesn't use the same compiler normal VS builds use by default.
Also all of my code only works on VS15.7 because that's what I have installed and I don't like switching VS updates mid-prototype because of all kinds of unexpected dependencies that can break if the bits on your machine don't exactly match the bits in the branch.
The VS-add-in for the rich documentation file is a separate project I just whipped up for the demo, it's not rigorous by any stretch and I hadn't had any plans to version either it or the demo projects themselves but maybe I should just so people can see behind the curtain a little.
Yes, it has taken up a good chunk of time but not excessively so. It's something I've wanted to deep dive into for a while now and I'm very pleased with the results. I'm already off to the next prototype which is something I've been stewing on since about 2010 :) I think it's even cooler.
@DualBrain I was thinking, it should assume it is a console application. Unless it detects that some form of UI form is to be created.
You don't have to assume anything. Console apps and WinForms apps are different project templates and already have different sets of project-level imports. WinForms apps have System.Drawing
for example while Console apps don't. But my thoughts on importing types is that I'm less confident that every or even most files in a project need direct access to the console. Usually in my apps there are a few types that need Console
members a lot and then a lot that don't so I prefer to do the import on a per file basis. Certainly the main code file in the console app project template could include this import explicitly as well.
@AnthonyDGreen: I'm already off to the next prototype which is something I've been stewing on since about 2010 :) I think it's even cooler.
Awesome keep 'em coming! :-)
Excited about watching only @AnthonyDGreen doing prototypes and did not hear from "Lucian Wischik" for quite sometime from VB desk. But please let us know how to use the prototypes ourselves or contribute to it any form. Is there any tracker for knowing if any of the prototypes have reached public release? For example in most (almost) cases we use webAPIs for all our business app development in workplaces which returns JSON. The JSON literals will be of immense help and will certainly give another life to VB. But how to use JSON Literals? If it makes to public release C# too would have that feature...
This proposal is a follow-up to the idea first introduced in #155 and #102
The goal of the proposal is to provide a new tool in VB.NET through which VB abstraction authors, e.g. library authors, educators/documentation writers, senior-developers/project-leads can create powerful yet streamlined experiences for end-developers requiring minimal or no boilerplate (ceremony) beyond use-case specific code. By removing the requirement for explicit type declarations, method declarations, and the like abstraction consumers (end-developers) can focus their attention on precisely the code that matters making those scenarios more approachable, more productive, and/or simply more enjoyable.
Here's a simple example of the syntax, with motivating scenarios to follow. The code depicted represents the entire code content of the program; nothing has been omitted.
Example 1 - Simple program
Motivating Scenarios
The above shows syntax but the feature is motivated far beyond "Hello, World!" programs. Using a prototype I have constructed 5 example scenarios and recorded a series of videos demonstrating them (~5-minutes each) showing a breadth of applications from educational to professional and from traditional desktop applications to the web. Each video has a companion blog post which expands on the scenario depicted and why it matters. They are as follows:
Scenario A: The Basics (Console) [3:29] Companion post
Scenario B: Python Notebooks/Xamarin Workbooks/Try.NET-style Rich text document intermixed with code [5:53] Companion post
Scenario C: An animated game-loop (Windows desktop) [5:07] Companion post
Scenario D: An ASPX/Razor-like ASP.NET Core-based "View Engine" (Web) [5:17] Companion post
Scenario E: A WYSIWYG ASP.NET Core REST service (Web) [5:16] Companion post
The first four are built solely using components of the proposal while the last combines it with the JSON Literals proposal. This is not an exhaustive list and other potential examples not explored include (but are not limited to):
I. Inferred Type Declarations ("Top-Level Declarations")
The proposed feature would allow type member declarations (methods, properties, fields, etc.) as well as
Inherits
andImplements
statements to appear directly in a compilation-unit (source file) without an explicit enclosing type declaration (class, module, etc). In such case a type declaration will be inferred, having a name inferred from the (file)name of the containing source file.Any member declaration at the compilation-unit level would be declared as a child of this inferred type declaration and any
Inherits
orImplements
statement would be considered to specify the base type and/or implemented interfaces of this type. In all other respects this is an ordinary VB type indistinguishable from one with an explicit declaration and may be combined with other partial definitions!Example - VB6-style "classless" code-behinds
Design Issues
If all top-level members in the file become members of the inferred type declaration, does that mean that top-level type declarations are now implicitly nested once a top-level
Sub
is added?Example - CodeSnippet.vb
What about Modules? They can't be nested today so if a module appears in an inferred type declaration what happens?
Options
What if a namespace appears in a file with an inferred type declaration?
Options
How do you parse top-level fields vs top-level local declarations?
It's a field if has field modifier, local otherwise,
Dim
andConst
prefer local.Note that this still allows you to declare type-level constants, you just have to explicitly add a class-level only modifier like
Public
.Should it always be a class?
Almost certainly
I went with a class because of the inheritance Scenarios C, D, & E. But someone noted it does produce awkwardness if you're not doing any inheritance because you have to explicitly mark things as
Shared
to be able to call them from other places in your program or to just explicitly defineMain
. What's most important for the "no ceremony" thing is that the top-level code and the top-level members both be either instance orShared
so they can interact without qualification.Could say the inferred type declaration has no kind so that it could be a partial part to a structure/module/etc (maybe).
Can I define extension methods in an inferred type?
No special rules here. If it's a class, then no, until such time as extension methods are allowed in classes.
Inferred type attributes?
See "VI.
Type
andMethod
Attribute Target" below.Inferred type modifiers?
In VB types are
Friend
by default, heritable, andPartial
isn't required on every definition. How will that effect the utility of this feature?Another way of putting the question is, should making the type
Public
sacrifice all of the brevity of the feature at large? I tend to think no. In fact, in Scenario E I ran into an issue where my inferred controller wasn't being recognized by MVC routing because it wasn'tPublic
. It was straightforward to work-around it by amending the routing rules to find non-public controllers but there could be other scenarios where this doesn't work. WinRT as I recall requires types to beNotInheritable
, for example.One pretty egregious work-around is to simply say that the inferred type declaration is like a partial declaration with no modifier. This means if you want to override the behavior you just declare a partial class elsewhere with the modifiers (and attributes) you need.
Another solution is some alternate syntax for specifying modifiers such as a pseudo-attribute:
The last option, which gets more compelling by the end of the document is described in "VII. A Magic Attribute That Lets The Abstract Author Prescribe Various Things"
How is the inferred type name inferred?
The compiler starts out with a full path. Any algorithm can be decided from that and it's a matter of deciding which parts to care about and which parts not to care about. But consider a few things:
.vb
extension.vbxhtml
, or.vbdoc
or whatever is an extension that helps VS out even if the compiler processes it normally (the compiler does not care about extensions).MainWindow.designer.vb
orMainPage.markup.vb
.MyCollection.Enumerator.vb
.With all that said what I prototyped is simple, take the path, get the filename, split it on dots, take the first part. Nothing clever to try and figure out a namespace from the folder structure, no inferring a nested type from dots, no special treatment of the
.vb
extension. If the name isn't a valid identifier it can either be an error or the compiler can do substitutions within reason like spaces with underscores or non-id characters with their unicode value or something. I'm open to suggestions.Can there by multiple partial inferred type declarations per type?
Sure!
Actually, there's just nothing to stop this. If the name is inferred from the path but without using every bit of the filename then both
Index.vb
andIndex.markup.vb
would infer the same name and be considered two parts to the same class. That's nothing special, it just "falls out" of how types are built. What will fail is if both parts define members with the same name/signature (like with explicit type declarations). This means that only one file gets to have an inferred method declaration since, as currently proposed they would both declare a method with the same name and signature. But as long as it's just non-conflicting top-level member declarations explicit top-level member declarations in one file and an inferred method declaration in another there's no problem here.Can an
Inherits
orImplements
statement appear anywhere in the file or must it be at the top?As prototyped, this is not a requirement and it's not technically problematic or expensive to go either way. It does make it harder to locate the
Inherits
orImplements
statement for API consumers, including the IDE.Given that inheritance affects name lookup it makes sense for the
Inherits
statement to precede any members or executable code. But if I did define any types in a separate namespace I could easily see myself putting such declarations before theInherits
statement or waaaaaaaaaaaaaaaay at the bottom of the file:Maybe we split the difference, a compilation-unit becomes:
II. Inferred Method Declarations ("Top-Level (Executable) Statements")
The proposal feature would allow executable statements (code which traditionally may only appear within method/accessor bodies) to appear directly in a compilation-unit (source file) without an explicit enclosing method declaration. In such case a method declaration will be inferred. Note that this inferred method declaration still constitutes a member declaration and as such its existence may itself cause an inferred type declaration. It is NOT proposed to allow executable statements to appear directly within a explicit type declaration.
The base class of the containing type of such a declaration may designate a particular
Overridable
method to be overridden by this method, in which case the inferred method declaration will infer the same name, signature, and accessibility as the method being overridden. Otherwise the name, signature, and accessibility shall be some set of defaults to be determined.Design Issues
Can top-level members reference top-level (non-field) locals?
No
Fields are fields and locals are locals.
What about Field/Property initializer execution order when interleaved with executable code?
Field and Property initializers execute normally (during construction) rather than when any inferred method declaration is executed
We could rewrite field initializers to execute inside of inferred method declarations so that this code works.
But why? This code could have just as easily used a local and gotten the correct behavior. If we change the behavior of fields it weakens their utility for the use cases they are appropriate for. Scenario C makes use of a lot of fields and properties that would be broken by this idea.
Does the inferred method have the
Async
and/orIterator
modifiers?Syntactically
Await
andYield
are always keywords inside of an inferred method though semantically they may be invalid.This is completely new code so there's no reason for
Await
to be an identifier.Yield
is arguable for finance apps but for now it's the same way. The question isn't whether those parse as keywords, they always will, but rather whether it's legal to use those keywords inside such methods.VB does not at this time support
Async Sub Main
. In part I initially thought of this proposal as a possible alternative to making approaching async easier. While this proposal would do so I don't think it necessarily precludes supporting a properAsync Sub Main
. Certainly, the documentation scenario says there will be cases whereAwait
is valuable in these methods. Scenarios B, D, and E all could have usedAwait
.In the case that the inferred method overrides a base method this is easy enough. The method just has to return the correct type:
If base method returns...
Task
orTask(Of T)
, method is anAsync Function
andAwait
is legal.IEnumerable
/IEnumerator
/IEnumerable(Of T)
/IEnumerator(Of T)
(etc), method is anIterator
method andYield
is legal.IAsyncEnumerator
,ValueTask(Of T)
(etc), decide appropriatelySub
but NEVER anAsync Sub
Alternately, see "VII. A Magic Attribute That Lets The Abstract Author Prescribe Various Things".
In the case that there is no base method there are a few options:
<Async Function() As Task(Of Object)>
and suppress diagnostics if the function doesn't return a value or fails to useAwait
.Sub
(or<Function() As Integer>
).Option 1 gives good flexibility in most cases because whoever calls this code can use or not use the return value if any and/or wait for the method to execute synchronously.
Option 2 is the most flexible but is problematic since the method signature, specifically its return type can change based on its content. That's unprecedented in the compiler and I'm not enthusiastic about breaking new ground here.
Option 3 is the simplest and means in the very simple case, a simple program won't be able to use
Await
which is important when learning about I/O in .NET.Another way of looking at the problem is this. If
Async Main
style tutorials are the key scenario to consider, we can get that for free with appropriate base-types in a library (e.g. the VB runtime). Just set the default base type to e.g.Microsoft.VisualBasic.ApplicationServices.AsyncConsoleApp
and those programs will work fine even if we go with Option 3. The question is whether to require that or whether the compiler does the work that that base class would have.Are
Exit Sub
,Exit Function
,Return
statements valid in an inferred method?As long as they are of the right kind for that method, yes, it's an ordinary method.
If the inferred method is a function, is the return variable valid to use inside?
Yes, it's an ordinary method.
The method has an utter-able name; it's not like an
Operator
or a lambda. It's arbitrary to disallow this in this one case and the compiler has a lot of assumptions about functions always having one.Does an inferred method constitute user-code for the purpose of
CallerInfo
attributes?Yes, it's an ordinary method..
This isn't a compiler-generated method-body (like an auto-prop) invoking a method; the code that causes the inferred method to come into being is user-code. Crippling
CallerInfo
in this case serves no one.Can you overload the inferred method?
Yes, it's an ordinary method.
How does
GetDeclaredSymbol
work?In order to get the Nav bar working here's what I implemented. The inferred method uses the
CompilationUnitSyntax
as its declaring syntax because there is no otherSyntaxNode
that would work. The inferred type also uses theCompilationUnitSyntax
as one of its declaring syntax references. So, obviously consumers will pass those nodes to theSemanticModel
to get back to theISymbol
for those entities. When passing a compilation-unit node toGetDeclaredSymbol
here's what you get back:IMethodSymbol
is returned.INamedTypeSymbol
is returned.All of these situations are new and some invariants have been stretched or broken. As I recall,
GetDeclaredSymbol
either always returns null for a givenSyntaxNode
type or always returns non-null. In places likeFor
loops where thei
inFor i = 1 To 10
could be declaring ani
or referring to an existing one we opted to just say it never declares a variable but rather always refers to one (though that one may also be implicitly declared) rather than sometimes returning something and sometimes not.Further, the type of symbol returned for a given syntax type has always been one kind and in this case it's one of two. But,
CompilationUnitSyntax
is/can be pretty special. We'd have to look at the impact on IDE code (and others) to understand whether this design is elegant or a nightmare for API consumers.How does the language determine which method an inferred method declaration will override?
Options
Execute
, and will override a base method by name-only. If more than one method namedExecute
exists in the base classes it is ambiguous.It's important that the compiler check each level of the hierarchy in turn so that a more derived type can change the default override method. Scenario D does this when on file inherits
Layout
and defines another placeholder for content.I started out with Option 1 but the name appearing in the nav bar is intuitively the most informative piece of information you can have when dropping code into one of these overrides. Requiring all of them to use the generic name
Execute
instead of meaningful names seems wrong.Then I moved to Option 2 with the
DefaultOverrideMethodAttribute
idea, which is better. The only problem is that it requires enumerating all methods and realizing their attributes to determine what to override.Option 3 is more inline with how indexers work. There's an attribute applied at the type level that tells you the name of the property that is the default property for that type. This would simplify the code to find such a method a lot and there are other things which might go into such an attribute, see "VII. A Magic Attribute That Lets The Abstract Author Prescribe Defaults"
III. Pattern-Based Non-Form-Derived Start-up Types
Startable non-Form types is demonstrated in Scenario B video and in it different types are specified as the start-up type at different points in the video to show why you might use a different type at different times. Other scenarios might include test runners/harnesses, etc., in non-documentation projects.
Today the entry-point to an executable VB program is determined one of several ways:
Shared
method with the correct nameMain
and signature. If there is a unique candidate across the entire compilation (Project), that is the entry-point./main:
compiler option.In the case that it is specified with
/main:
the type specified or one of its base-types must declare a validMain
method OR if the type inheritsSystem.Windows.Forms.Form
(directly or indirectly) an entry point will be generated, roughly equivalent to the following:I propose generalizing this special case further:
T
) is NOT derived fromForm
, andT
or one of its base types has an accessibleShared
Run
method taking a parameter assignable fromT
, the compiler will synthesize an entry point passing an instance ofT
toRun
(usingMy
default instances if available, orNew T
otherwise).Run
is a function returning anInteger
the synthesizedMain
method will be a function returning the value of the invocation ofRun
, otherwise the synthesizedMain
method will be a sub callingRun
.This indirection is necessary for several reasons. First, it allows the
Run
method to do any scenario-specific setup and control exactly when the top-level code gets run and what happens before or after and what arguments, if any get passed to it.In Scenario C the
Execute
method isn't run once at the start of the program, it's actually run every frame and "starting" the app is independent of invoking that method, though in that scenario this is accomplished by the fact that the game loop is implemented as a type deriving fromForm
so the existing start-up code that callsApplication.Run
is called.Another scenario that would benefit from this is
Async Main
. The compiler would only need to call (e.g.)AsyncConsoleApp.Run(New Program)
and theRun
method would have the smarts to callExecuteAsync().GetAwaiter().GetResult()
. It also means that a different base class could use a different scheduling strategy and could provide its own message pump, if so inclined.Secondary Proposals
IV. Default inferred type base class compilation option
Default base types for inferred type declarations is demonstrated in Scenario D above.
I propose a new compiler option allowing one to specify the fully-qualified name of a type to be used by default as the base type of an inferred type declaration if and only if no other base type is explicitly specified either within the inferred type declaration or any of its other partial definitions.
Note: That just as with project-level imports this name can specify a generic instantiation so
/defaultinferredtypebaseclass:System.Collections.Generic.Dictionary(Of String, Object)
is perfectly valid.While one could always specify a base type explicitly, that constitutes boilerplate; especially in projects where a significant number of files use inferred type declarations. In Scenario D potentially dozens of types might all inherit from
View
. In WinForms the user-file is kept uncluttered like this by putting theInherits
statement in thePartial
declaration in the designer-generated file by default.Design Issues
What should it be named?
V. Implicit Return Expression Statements ("Top-Level Expressions")
Implicit return expressions are demonstrated in Scenario D and Scenario E above.
Example - TopBuyers2018.vb
When combined with VBs existing MyGroupCollection extensibility this becomes a very fast way to build up suite of queries because the compiler itself already knows how to collect all types of a given base class and manage their instances:
My.Queries.TopBuyers2018
could come into existence simply by defining this file.I propose that some set of expressions in VB are often of sufficient size and complexity to both be the central content of their containing method AND to benefit from having the maximum horizontal space for their use, particularly in an inferred method declaration, and that it should be permissible in an inferred method declaration (at least) to use such expressions in an expression statement and have that statement act as an implicit return statement returning the value of that expression.
Specifically, expressions that I believe fit this criteria at this time include:
I also believe that if that proposal were accepted, JSON literal object and array expressions could fit this criteria when writing web services. This allows for an immersive authoring experience and, in the case of an XML literal may have practical implications on the output because indentation contributes whitespace to the constructed object.
Functional languages like F# lean hard into this and I don't think VB will or should ever go that deep but there could be some measured toe-dipping here.
Design issues
Should it be all expressions?
No.
Practically it can't be because there are some ambiguities between expressions and statements. The most well-known example is
a = b
which is either an assignment or a comparison depending on how it's interpreted. But also, from a parsing (and reading) perspective, the first token of most valid VB statements is a keyword describing the kind of statement it is or an identifier for assignments and invocations. There are exceptions, such as numeric labels, but it's pretty unknown what ambiguities arise if that convention is dropped entirely.In the case of LINQ or a hypothetical JSON literal there is no ambiguity, these are safe. But in the case of a top-level XML elements it's ambiguous whether a line starting with
<
is the beginning of an XML literal or a attribute specification and I haven't yet thought my way out of how to resolve it elegantly. Even with an XML namespace it looks the same as an attribute target specifier and I don't we would want to key the parsing on whether the namespace prefix is a valid target anyway as that would tie our hands in the future if more attribute targets are added. It's not ambiguous with XML documents though, which is good which is why Scenario D specifically uses whole documents.Arguably it could be fine for parenthesized expressions, which would consequently let all the other expressions in. This is something that was discussed for VB Interactive submissions as well as the Immediate Window. It would have to be weighed against any possible target-type-inference/expression-classification issues (e.g. if the return type of the containing method is delegate/array-literal/interpolated string).
Should this be expanded to cases outside of inferred method declarations?
Maybe.
Does it have to be the last expression in the compilation-unit and and can it be inside a block?
There's potentially some ambiguity with XML elements because "technically" VB
static
local variable declarations can have attributes, but they are ignored is provided, aren't actually bound, no tooling, don't even have to refer to real attributes. I believe it's a parsing artifact from the native compilers which was maintained for back-compat. If the attribute appears on a non-static local it's an error, so it could probably be worked around.VI.
Type
andMethod
Attribute TargetsBecause under this proposal, type and method declarations may be inferred there's no place to attach attributes which would otherwise appear on an explicit declaration. Similar to the approach one might take when, say, applying an attribute to the implicitly declared backing field of an auto-prop, this would be a good scenario to extend VBs existing
Assembly
andModule
attribute target specifiers to include some form ofType
andMethod
targets. They would still appear at the file-level but would be taken to apply to the inferred declarations.At this time I don't have concrete scenarios for this at the top of my mind with the exception of Scenario E. Because ASP.NET Core can also make use of attribution to describe URL routing it would be valuable to be able to specify those attributes on a stand-alone file. In my demo I designed my base classes such that this wasn't necessary but there are certainly other situations out there.
Type
andMethod
attribute targets are orthogonal to this proposal and aren't necessary for it. This is just a note to keep in mind that doing this feature adds another place where such targets would potentially add value should they be needed/added later.VII. A Magic Attribute That Lets The Abstract Author Prescribe Defaults
As I worked through the scenarios there were an increasing number of knobs an abstraction author might want to configure and it could be beneficial to have a centralized attribute for configuring them all. For example:
Public
for controllers,NotInheritable
for WinRT types)HttpGetAttribute
).Async
,Iterator
)HttpRouteAttribute
).Main
method to make an inferred type a start-up type, any attributes that should go on such aMain
method such asSTAThreadAttribute
.Because this feature is a tool for abstraction authors to create particular experiences for end-developers it's conceivable that there might be a richer
InferredClassAndMethodConfigurationAttribute
which makes all that information available to the compiler so that those decisions can remain in the hands of abstraction authors.The alternative is to pick a set of defaults with various escape hatches for abstraction consumers (e.g. attribute targets) at the cost of increasing the complexity of the abstractions we're trying to simplify in the first place.
Today attribute inheritance is largely governed by the setting of
AttributeUsageAttribute.Inherited
which gives power to the developer defining the attribute to decide how it behaves. I think there's a case to be made that there should also be a mechanism for the developer applying an attribute to decide how it will behave for their derivatives.By analogy, a base class author can mark a method
Overridable
but another derived base class author can revisit and decide the method isNotOverridable
or evenMustOverride
. It would be interesting to look into that.The issue of attribute inheritance was entirely motivated by how ASP.NET MVC finds and uses attributes in Scenario E. However, the biggest issue with Scenario E was that, by default, controllers have to be
Public
and types in VB areFriend
by default. To work around this issue I used MVCs extensibility to add a filter for controllers that permits non-public controllers but the documentation indicated there may be similar issues with route attributes that would require similar workarounds.