リンクのクイック追加または別れの参照の追加

最近、私は非常に長い間悩まされてきた1つの問題を解決しました。 その本質は、スタジオが探している場所のいずれかからアセンブリを取得する場合、Visual Studioの[参照の追加]ダイアログは必要ないということです。 スタジオ自体がこれらのアセンブリ内のすべての名前空間にインデックスを付けることができ、 using Biztalk



using Biztalk



して作成using Biztalk



ときにリンクを自動的に追加する機会が与えられるため、これは必要ありません。 スタジオはこれを行う方法を知らないので、私は彼女を助けなければなりませんでした。





アイデア自体はシンプルで、2つの部分で構成されています。





索引付け



名前空間とアセンブリファイルパスのベースは数秒で完了します。 唯一のトリックは、 Assembly.ReflectionOnlyLoad()



などの依存関係の代わりにCecilを使用することです。これは、依存関係などをロードしようとします。 すべての型をすばやく見つけ、それらの名前空間をHashSet



書き留め、データベースにすべて入れます。 どうやって? これについては今から話します。



まず、参照の追加が使用するファイルへのパスは、レジストリとPublicAssembliesフォルダーの少なくとも2つの場所にあります。 レジストリにリストされているフォルダーを見つけるために、次のコードを作成しました。







public static IEnumerable< string > GetAssemblyFolders()<br/>

{<br/>

string [] valueNames = new [] { string .Empty, "All Assemblies In" };<br/>

string [] keyNames = new []<br/>

{<br/>

@"SOFTWARE\Microsoft\.NETFramework\AssemblyFolders" ,<br/>

@"SOFTWARE\Wow6432Node\Microsoft\.NETFramework\AssemblyFolders" <br/>

};<br/>

var result = new HashSet< string >();<br/>

foreach ( var keyName in keyNames)<br/>

{<br/>

using ( var key = Registry.LocalMachine.OpenSubKey(keyName))<br/>

{<br/>

if (key != null )<br/>

foreach ( string subkeyName in key.GetSubKeyNames())<br/>

{<br/>

using ( var subkey = key.OpenSubKey(subkeyName))<br/>

{<br/>

if (subkey != null )<br/>

{<br/>

foreach ( string valueName in valueNames)<br/>

{<br/>

string value = (subkey.GetValue(valueName) as string ?? string .Empty).Trim();<br/>

if (! string .IsNullOrEmpty( value ))<br/>

result.Add( value );<br/>

}<br/>

}<br/>

}<br/>

}<br/>

}<br/>

}<br/>

return result;<br/>

}<br/>







最初は、私のためにほとんど機能しませんでした。 32ビットシステムと64ビットシステムのキーは異なります。 ここでも、64ビットシステムへの移行に伴い、より良いコードを記述し始めたことに気付きました。



PublicAssembliesフォルダーを見つけるには、まずVisual Studioがインストールされている場所を見つける必要があります。



public static string GetVS9InstallDirectory()<br/>

{<br/>

var keyNames = new string []<br/>

{<br/>

@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\Setup\VS" ,<br/>

@"SOFTWARE\Microsoft\VisualStudio\9.0\Setup\VS" <br/>

};<br/>

foreach ( var keyName in keyNames)<br/>

{<br/>

using ( var key = Registry.LocalMachine.OpenSubKey(keyName))<br/>

{<br/>

if (key != null )<br/>

return key.GetValue( "ProductDir" ).ToString();<br/>

}<br/>

}<br/>

return null ;<br/>

}<br/>







フォルダーのリストがあると、それぞれのすべてのDLLファイルを検索し、それらにインデックスを付けることができます。 [参照の追加]ダイアログに常に表示されるフォルダーに加えて、独自のフォルダーを追加できます。これは便利です。



using ( var dc = new StatsDataContext())<br/>

{<br/>

var dirs = new HashSet< string >();<br/>

dirs.Add( @"C:\Program Files (x86)\JetBrains\ReSharper\v4.5\Bin" );<br/>

foreach ( var dir in GetAssemblyFolders()) dirs.Add(dir);<br/>

dirs.Add(Path.Combine(GetVS9InstallDirectory(), @"Common7\IDE\PublicAssemblies" ));<br/>

foreach ( string dir in dirs.Where(Directory.Exists))<br/>

{<br/>

string [] files = Directory.GetFiles(dir, "*.dll" );<br/>

var entries = new HashSet<Namespace>();<br/>

foreach ( string file in files)<br/>

{<br/>

var ns = AddNamespacesFromFile(file);<br/>

foreach ( var n in ns)<br/>

entries.Add(n);<br/>

}<br/>

dc.Namespaces.InsertAllOnSubmit(entries);<br/>

}<br/>

dc.SubmitChanges();<br/>

}<br/>







AddNamespacesFromFile()



メソッドを使用して追加を行います。これは、私が書いたようにMono.Cecilを使用します。



private static IEnumerable<Namespace> AddNamespacesFromFile( string file)<br/>

{<br/>

HashSet<Namespace> result = new HashSet<Namespace>();<br/>

try <br/>

{<br/>

var ad = AssemblyFactory.GetAssembly(file);<br/>

foreach (ModuleDefinition m in ad.Modules)<br/>

{<br/>

foreach (TypeDefinition type in m.Types)<br/>

{<br/>

if (type.IsPublic && ! string .IsNullOrEmpty(type.Namespace))<br/>

{<br/>

result.Add( new Namespace<br/>

{<br/>

AssemblyName = ad.Name.Name,<br/>

AssemblyVersion = ad.Name.Version.ToString(),<br/>

NamespaceName = type.Namespace,<br/>

PhysicalPath = file<br/>

});<br/>

}<br/>

}<br/>

}<br/>

}<br/>

catch <br/>

{<br/>

// it's okay, probably a non-.Net DLL

}<br/>

return result;<br/>

}<br/>







ベースの充填で、それだけです。 さらに、結果を使用できますが、データを更新して新しいパスを追加できるバックグラウンドユーティリティも作成しました。



使用する



より良いオプションがないため、ReSharperのコンテキストアクションとしてリンクの追加を実装しました。 アイデアは簡単です-ユーザーは、 using Biztalk;



して行のBiztalk



という単語にBiztalk



を合わせusing Biztalk;



プロジェクトにリンクが自動的に追加される要素を選択すると、魔法のメニューが表示されます。



CA自体は、有用なクラスCSharpContextActionBase



から継承しますCSharpContextActionBase



内部では、「適用可能性」のチェックは別として、スマートなことは何も起こりません。 データベースはSELECT * from Namespaces where NamespaceName LIKE '%BizTalk%'



単純なSELECT * from Namespaces where NamespaceName LIKE '%BizTalk%'



を使用して検索されます。 数千個の要素を持つベース(試してみると、おそらく1万個)には、このアプローチが適切です。



protected override bool IsAvailableInternal()<br/>

{<br/>

items = EmptyArray<IBulbItem>.Instance;<br/>

var element = GetSelectedElement<IElement>( false );<br/>

if (element == null )<br/>

return false ;<br/>

var parent = element.ToTreeNode().Parent;<br/>

if (parent == null || parent.GetType().Name != "ReferenceName" || parent.Parent == null <br/>

|| string .IsNullOrEmpty(parent.Parent.GetText()))<br/>

return false ;<br/>

string s = parent.Parent.GetText();<br/>

if ( string .IsNullOrEmpty(s))<br/>

return false ;<br/>

var bulbItems = new HashSet<RefBulbItem>();<br/>

using ( var conn = new SqlConnection(<br/>

"Data Source=(local);Initial Catalog=Stats;Integrated Security=True" ))<br/>

{<br/>

conn.Open();<br/>

var cmd = new SqlCommand(<br/>

"select * from Namespaces where NamespaceName like '%" + s + "%'" , conn);<br/>

using ( var r = cmd.ExecuteReader())<br/>

{<br/>

int count = 0;<br/>

while (r.Read())<br/>

{<br/>

bulbItems.Add( new RefBulbItem(<br/>

provider,<br/>

r.GetString(2).Trim(),<br/>

r.GetString(3).Trim(),<br/>

r.GetString(4).Trim()));<br/>

count++;<br/>

}<br/>

if (count > 0)<br/>

{<br/>

items = System.Linq.Enumerable.ToArray(<br/>

System.Linq.Enumerable.ThenBy(<br/>

System.Linq.Enumerable.OrderBy(<br/>

bulbItems,<br/>

i => i.AssemblyName),<br/>

i => i.AssemblyVersion));<br/>

return true ;<br/>

}<br/>

}<br/>

}<br/>

return false ;<br/>

}<br/>







興味深いことはすべてBulbItem



ahで発生します。つまり、コンテキストメニューの呼び出し中に表示される黄色の電球です。 電球自体は特定のPOCOであり、適切なタイミングで特定のアセンブリへのリンクを追加できます。



protected override void ExecuteBeforeTransaction(ISolution solution,<br/>

JetBrains.TextControl.ITextControl textControl, IProgressIndicator progress)<br/>

{<br/>

var project = provider.Project;<br/>

if (project == null ) return ;<br/>

var fileSystemPath = FileSystemPath.TryParse(path);<br/>

if (fileSystemPath == null ) return ;<br/>

var assemblyFile = provider.Solution.AddAssembly(fileSystemPath);<br/>

if (assemblyFile == null ) return ;<br/>

var cookie = project.GetSolution().EnsureProjectWritable(project, out project, SimpleTaskExecutor.Instance);<br/>

QuickFixUtil.ExecuteUnderModification(textControl,<br/>

() => project.AddModuleReference(assemblyFile.Assembly),<br/>

cookie);<br/>

}<br/>







上記のコードは、JetBrainsチームのメンバー( プレーナー -ありがとう!) 私自身は正しい道を見つける忍耐を持っていませんでした。



おわりに



この機能を実装することでどれだけの時間を節約したかはわかりませんが、「座って参照を追加する」というスタイルの頭痛は確実に少なくなりました。 また、お気に入りのアセンブリセット(DI、モック、検証、ユーティリティなど)を使用してプロジェクトをコンパイルするのがはるかに簡単になりました。 ■



All Articles