Kennen Sie das?
Sie haben eine tolle Bibliothek mit Extension-Methoden geschrieben aber Intellisense ist immer voll mit Einträgen, auch wenn Sie sie nicht brauchen?
Gibt es denn keine bessere Lösung?
Doch!
Naja, teilweise. C# und VB behandeln Extensions ja unterschiedlich:
Das kompiliert zwar in beiden Fällen zu Klassen, die statische Methoden beinhalten, aber die sind leicht unterschiedlich:
Es gibt folgende Unterschiede:
Die C#-Klasse ist abstrakt und "BeforeFieldInit".
Die VB-Klasse hat zusätzlich das StandardModule-Attribut.
Die C#-Methode ist "HideBySig".
Das StandardModule-Attribut sorgt in VB dafür, dass alle Member des Moduls sichtbar sind, wenn das Modul sichtbar ist. Aber: Extension-Methoden, die in C# geschrieben wurden, können auch in VB verwendet werden, obwohl das Attribut fehlt.
In einer unerwarteten Eingebung heute Nachmittag kam mir der Gedanke, dass man ja Mono.Cecil verwenden könnte, die VB-Klassen nachträglich so umzubauen, dass sie wie die C#-Klassen aussehen.
Gesagt, getan. Das folgende ist eine Konsolenanwendung. Das Befehlszeilenargument ist ein Pfad zu einer Assembly, in der alle Module umgebastelt werden.
Es ist nicht unbedingt nötig, die
Der aufmerksame Leser wird anmerken, dass das nur mit bereits kompilierten Assemblies funktioniert. Das stimmt, deshalb "teilweise" oben. VisualStudio hält sich natürlich an seine Regeln was den Code im aktuellen Projekt betrifft. Aber für Bibliotheken, auf die verwiesen wird, funktioniert das ziemlich gut:
Man kann auch ein PostBuild-Event für die Projektmappe hinzufügen:
Funktioniert auch in den Express-Versionen: Die .vbproj-Datei (bzw. .csproj) öffnen und ganz unten ein bisschen was einfügen:
Eine andere Methode, Extensions in Intellisense auszublenden, hat ErfinderDesRades schon mal gezeigt:
EditorBrowsableState.Never für Extension-Methods
Das funktioniert aber nur einwandfrei, solange sich das Modul mit den Extensions im aktuellen Projekt befindet. In einem anderen Projekt werden die Extensions dann garnicht mehr angezeigt.
Man kann da möglicherweise was mit dem Präprozessor machen und immer umschalten:
Also wenn man gerade an der Bibliothek arbeitet InvisibleModules auf True stellen und beim Release auf False:
Das ist leider wartungstechnisch etwas aufwändiger.
Sie haben eine tolle Bibliothek mit Extension-Methoden geschrieben aber Intellisense ist immer voll mit Einträgen, auch wenn Sie sie nicht brauchen?
Gibt es denn keine bessere Lösung?
Doch!
Naja, teilweise. C# und VB behandeln Extensions ja unterschiedlich:
Das kompiliert zwar in beiden Fällen zu Klassen, die statische Methoden beinhalten, aber die sind leicht unterschiedlich:
CIL-Quellcode
- // VB
- .class public auto ansi sealed Extensions extends Object
- {
- .custom instance void StandardModuleAttribute::.ctor() = (01 00 00 00)
- .custom instance void ExtensionAttribute::.ctor() = (01 00 00 00)
- .method public static void Foo (int32 Target) cil managed
- {
- .custom instance void ExtensionAttribute::.ctor() = (01 00 00 00)
- //...
- }
- }
- // C#
- .class public auto ansi abstract sealed beforefieldinit Extensions extends Object
- {
- .custom instance void ExtensionAttribute::.ctor() = (01 00 00 00)
- .method public hidebysig static void Foo (int32 Target) cil managed
- {
- .custom instance void ExtensionAttribute::.ctor() = (01 00 00 00)
- //...
- }
- }
Es gibt folgende Unterschiede:
Die C#-Klasse ist abstrakt und "BeforeFieldInit".
Die VB-Klasse hat zusätzlich das StandardModule-Attribut.
Die C#-Methode ist "HideBySig".
Das StandardModule-Attribut sorgt in VB dafür, dass alle Member des Moduls sichtbar sind, wenn das Modul sichtbar ist. Aber: Extension-Methoden, die in C# geschrieben wurden, können auch in VB verwendet werden, obwohl das Attribut fehlt.
In einer unerwarteten Eingebung heute Nachmittag kam mir der Gedanke, dass man ja Mono.Cecil verwenden könnte, die VB-Klassen nachträglich so umzubauen, dass sie wie die C#-Klassen aussehen.
Gesagt, getan. Das folgende ist eine Konsolenanwendung. Das Befehlszeilenargument ist ein Pfad zu einer Assembly, in der alle Module umgebastelt werden.
VB.NET-Quellcode
- Public Class Program
- Public Const InvalidArgCount = 2
- Public Const NonExistingAssembly = 3
- Public Const AssemblyNotLoaded = 4
- Public Const AssemblyNotSaved = 5
- Public Shared Function Main(Args As String()) As Integer
- 'Assembly öffnen
- 'Für alle Typen mit StandardModuleAttribute
- ' Attribute hinzufügen: abstract, beforefieldinit
- ' CustomAttribute entfernen: StandardModuleAttribute
- ' Für alle Methoden im Typ mit ExtensionAttribute
- ' Attribute hinzufügen: hidebysig
- 'Assembly speichern
- If Args.Length < 1 Then
- Console.WriteLine("Zu wenig Argumente")
- Return InvalidArgCount
- End If
- Dim AssemblyFilePath = Args(0)
- If Not System.IO.File.Exists(AssemblyFilePath) Then
- Console.WriteLine("Datei existiert nicht")
- Return NonExistingAssembly
- End If
- Dim Asm As Mono.Cecil.AssemblyDefinition
- Try
- Asm = Mono.Cecil.AssemblyDefinition.ReadAssembly(AssemblyFilePath)
- Catch ex As Exception
- Console.WriteLine("Fehler beim Laden der Assembly: " & ex.GetType.Name)
- Console.WriteLine(ex.Message)
- Return AssemblyNotLoaded
- End Try
- ProcessTypes(Asm.Modules.SelectMany(Function(i) i.Types))
- Try
- Asm.Write(AssemblyFilePath)
- Catch ex As Exception
- Console.WriteLine("Fehler beim Speichern der Assembly: " & ex.GetType.Name)
- Console.WriteLine(ex.Message)
- Return AssemblyNotSaved
- End Try
- Console.WriteLine("Fertig")
- Return 0
- End Function
- Private Shared Sub ProcessTypes(Types As IEnumerable(Of Mono.Cecil.TypeDefinition))
- For Each Type In Types
- Dim ModuleAttributeMatch = Type.CustomAttributes.SingleOrDefault(Function(i) i.AttributeType.FullName = "Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute")
- If ModuleAttributeMatch IsNot Nothing Then
- Console.WriteLine("Modul: " & Type.FullName)
- Type.Attributes = Type.Attributes Or Mono.Cecil.TypeAttributes.Abstract Or Mono.Cecil.TypeAttributes.BeforeFieldInit
- For Each Method In Type.Methods
- If Not Method.IsConstructor AndAlso Not Method.IsGetter AndAlso Not Method.IsSetter Then
- Dim ExtensionAttributeMatch = Method.CustomAttributes.SingleOrDefault(Function(i) i.AttributeType.FullName = "System.Runtime.CompilerServices.ExtensionAttribute")
- If ExtensionAttributeMatch IsNot Nothing Then
- Console.WriteLine(" Extension: " & Method.Name)
- Method.Attributes = Method.Attributes Or Mono.Cecil.MethodAttributes.HideBySig
- End If
- End If
- Next
- Type.CustomAttributes.Remove(ModuleAttributeMatch)
- End If
- ProcessTypes(Type.NestedTypes)
- Next
- End Sub
- End Class
Es ist nicht unbedingt nötig, die
Attributes
(nicht zu verwechseln mit CustomAttributes
, die Namenskonventionen sind hier ungünstig gewählt) der Klassen und Methoden anzupassen. Es reicht, das StandardModule-Attribut zu entfernen, um die Methoden in Intellisense auszublenden. Welche Nebeneffekte das aber sonst noch haben könnte, hab ich nicht getestet.Der aufmerksame Leser wird anmerken, dass das nur mit bereits kompilierten Assemblies funktioniert. Das stimmt, deshalb "teilweise" oben. VisualStudio hält sich natürlich an seine Regeln was den Code im aktuellen Projekt betrifft. Aber für Bibliotheken, auf die verwiesen wird, funktioniert das ziemlich gut:
Man kann auch ein PostBuild-Event für die Projektmappe hinzufügen:
"$(SolutionDir)PostBuildRemoveModules\bin\Release\PostBuildRemoveModules.exe" "$(TargetPath)"
Funktioniert auch in den Express-Versionen: Die .vbproj-Datei (bzw. .csproj) öffnen und ganz unten ein bisschen was einfügen:
XML-Quellcode
- <!-- ... -->
- </ItemGroup>
- <ItemGroup>
- <Service Include="{94E38DFF-614B-4CBD-B67C-F211BB35CE8B}" />
- </ItemGroup>
- <ItemGroup />
- <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
- <!-- Das hier einfügen: -->
- <PropertyGroup>
- <PostBuildEvent>"$(SolutionDir)PostBuildRemoveModules\bin\Release\PostBuildRemoveModules.exe" "$(TargetPath)"</PostBuildEvent>
- </PropertyGroup>
- </Project>
Eine andere Methode, Extensions in Intellisense auszublenden, hat ErfinderDesRades schon mal gezeigt:
EditorBrowsableState.Never für Extension-Methods
Das funktioniert aber nur einwandfrei, solange sich das Modul mit den Extensions im aktuellen Projekt befindet. In einem anderen Projekt werden die Extensions dann garnicht mehr angezeigt.
Man kann da möglicherweise was mit dem Präprozessor machen und immer umschalten:
Also wenn man gerade an der Bibliothek arbeitet InvisibleModules auf True stellen und beim Release auf False:
Das ist leider wartungstechnisch etwas aufwändiger.
"Luckily luh... luckily it wasn't poi-"
-- Brady in Wonderland, 23. Februar 2015, 1:56
Desktop Pinner | ApplicationSettings | OnUtils
-- Brady in Wonderland, 23. Februar 2015, 1:56
Desktop Pinner | ApplicationSettings | OnUtils