Browse Source

Fixed several issues with "Open in ILSpy" menu shown on non-applicable nodes. Refactored the commands.

pull/1167/head
Andreas Weizel 7 years ago
parent
commit
809354f175
  1. 45
      ILSpy.AddIn/Commands/AssemblyReferenceForILSpy.cs
  2. 53
      ILSpy.AddIn/Commands/NuGetReferenceForILSpy.cs
  3. 2
      ILSpy.AddIn/Commands/OpenCodeItemCommand.cs
  4. 25
      ILSpy.AddIn/Commands/OpenILSpyCommand.cs
  5. 16
      ILSpy.AddIn/Commands/OpenProjectOutputCommand.cs
  6. 135
      ILSpy.AddIn/Commands/OpenReferenceCommand.cs
  7. 54
      ILSpy.AddIn/Commands/ProjectItemForILSpy.cs
  8. 78
      ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs
  9. 5
      ILSpy.AddIn/ILSpy.AddIn.csproj
  10. 21
      ILSpy.AddIn/ILSpyAddIn.vsct
  11. 12
      ILSpy.AddIn/ILSpyAddInPackage.cs
  12. 50
      ILSpy.AddIn/Utils.cs

45
ILSpy.AddIn/Commands/AssemblyReferenceForILSpy.cs

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VSLangProj;
namespace ICSharpCode.ILSpy.AddIn.Commands
{
/// <summary>
/// Represents an assembly reference item in Solution Explorer, which can be opened in ILSpy.
/// </summary>
class AssemblyReferenceForILSpy
{
Reference reference;
AssemblyReferenceForILSpy(Reference reference)
{
this.reference = reference;
}
/// <summary>
/// Detects whether the given selected item represents a supported project.
/// </summary>
/// <param name="itemData">Data object of selected item to check.</param>
/// <returns><see cref="AssemblyReferenceForILSpy"/> instance or <c>null</c>, if item is not a supported project.</returns>
public static AssemblyReferenceForILSpy Detect(object itemData)
{
return (itemData is Reference reference) ? new AssemblyReferenceForILSpy(reference) : null;
}
/// <summary>
/// If possible retrieves parameters to use for launching ILSpy instance.
/// </summary>
/// <param name="projectReferences">List of current project's references.</param>
/// <returns>Parameters object or <c>null, if not applicable.</c></returns>
public ILSpyParameters GetILSpyParameters(Dictionary<string, string> projectReferences)
{
if (projectReferences.TryGetValue(reference.Name, out var path))
return new ILSpyParameters(new[] { path });
return null;
}
}
}

53
ILSpy.AddIn/Commands/NuGetReferenceForILSpy.cs

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EnvDTE;
namespace ICSharpCode.ILSpy.AddIn.Commands
{
/// <summary>
/// Represents a NuGet package item in Solution Explorer, which can be opened in ILSpy.
/// </summary>
class NuGetReferenceForILSpy
{
ProjectItem projectItem;
NuGetReferenceForILSpy(ProjectItem projectItem)
{
this.projectItem = projectItem;
}
/// <summary>
/// Detects whether the given selected item represents a supported project.
/// </summary>
/// <param name="itemData">Data object of selected item to check.</param>
/// <returns><see cref="NuGetReferenceForILSpy"/> instance or <c>null</c>, if item is not a supported project.</returns>
public static NuGetReferenceForILSpy Detect(object itemData)
{
if (itemData is ProjectItem projectItem) {
var properties = Utils.GetProperties(projectItem.Properties, "Type");
if ((properties[0] as string) == "Package") {
return new NuGetReferenceForILSpy(projectItem);
}
}
return null;
}
/// <summary>
/// If possible retrieves parameters to use for launching ILSpy instance.
/// </summary>
/// <returns>Parameters object or <c>null, if not applicable.</c></returns>
public ILSpyParameters GetILSpyParameters()
{
var properties = Utils.GetProperties(projectItem.Properties, "Name", "Version", "Path");
if (properties[0] != null && properties[1] != null && properties[2] != null) {
return new ILSpyParameters(new[] { $"{properties[2]}\\{properties[0]}.{properties[1]}.nupkg" });
}
return null;
}
}
}

2
ILSpy.AddIn/Commands/OpenCodeItemCommand.cs

@ -47,7 +47,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -47,7 +47,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
if (symbol == null)
return;
var refs = GetReferences(roslynDocument.Project).Select(fn => fn.Value).Where(f => File.Exists(f)).ToArray();
OpenAssembliesInILSpy(refs, "/navigateTo:" + symbol.GetDocumentationCommentId());
OpenAssembliesInILSpy(new ILSpyParameters(refs, "/navigateTo:" + symbol.GetDocumentationCommentId()));
}
internal static void Register(ILSpyAddInPackage owner)

25
ILSpy.AddIn/Commands/OpenILSpyCommand.cs

@ -11,6 +11,18 @@ using Mono.Cecil; @@ -11,6 +11,18 @@ using Mono.Cecil;
namespace ICSharpCode.ILSpy.AddIn.Commands
{
public class ILSpyParameters
{
public ILSpyParameters(IEnumerable<string> assemblyFileNames, params string[] arguments)
{
this.AssemblyFileNames = assemblyFileNames;
this.Arguments = arguments;
}
public IEnumerable<string> AssemblyFileNames { get; private set; }
public string[] Arguments { get; private set; }
}
abstract class ILSpyCommand
{
protected ILSpyAddInPackage owner;
@ -36,18 +48,21 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -36,18 +48,21 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
return Path.Combine(basePath, "ILSpy.exe");
}
protected void OpenAssembliesInILSpy(IEnumerable<string> assemblyFileNames, params string[] arguments)
protected void OpenAssembliesInILSpy(ILSpyParameters parameters)
{
foreach (string assemblyFileName in assemblyFileNames) {
if (parameters == null)
return;
foreach (string assemblyFileName in parameters.AssemblyFileNames) {
if (!File.Exists(assemblyFileName)) {
owner.ShowMessage("Could not find assembly '{0}', please ensure the project and all references were built correctly!", assemblyFileName);
return;
}
}
string commandLineArguments = Utils.ArgumentArrayToCommandLine(assemblyFileNames.ToArray());
if (arguments != null) {
commandLineArguments = string.Concat(commandLineArguments, " ", Utils.ArgumentArrayToCommandLine(arguments));
string commandLineArguments = Utils.ArgumentArrayToCommandLine(parameters.AssemblyFileNames.ToArray());
if (parameters.Arguments != null) {
commandLineArguments = string.Concat(commandLineArguments, " ", Utils.ArgumentArrayToCommandLine(parameters.Arguments));
}
System.Diagnostics.Process.Start(GetILSpyPath(), commandLineArguments);

16
ILSpy.AddIn/Commands/OpenProjectOutputCommand.cs

@ -15,16 +15,12 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -15,16 +15,12 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
protected override void OnExecute(object sender, EventArgs e)
{
if (owner.DTE.SelectedItems.Count != 1) return;
var project = owner.DTE.SelectedItems.Item(1).Project;
var roslynProject = owner.Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == project.FileName);
string outputFileName = Path.GetFileName(roslynProject.OutputFilePath);
//get the directory path based on the project file.
string projectPath = Path.GetDirectoryName(project.FullName);
//get the output path based on the active configuration
string projectOutputPath = project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString();
//combine the project path and output path to get the bin path
OpenAssembliesInILSpy(new[] { Path.Combine(projectPath, projectOutputPath, outputFileName) });
if (owner.DTE.SelectedItems.Count != 1)
return;
var projectItemWrapper = ProjectItemForILSpy.Detect(owner.DTE.SelectedItems.Item(1));
if (projectItemWrapper != null) {
OpenAssembliesInILSpy(projectItemWrapper.GetILSpyParameters(owner));
}
}
internal static void Register(ILSpyAddInPackage owner)

135
ILSpy.AddIn/Commands/OpenReferenceCommand.cs

@ -3,6 +3,7 @@ using System.Collections.Generic; @@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using EnvDTE;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.Shell;
using Mono.Cecil;
using VSLangProj;
@ -17,109 +18,70 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -17,109 +18,70 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
{
}
protected override void OnBeforeQueryStatus(object sender, EventArgs e)
{
if (sender is OleMenuCommand menuItem) {
menuItem.Visible = false;
var selectedItemData = owner.GetSelectedItemsData<object>().FirstOrDefault();
if (selectedItemData == null)
return;
/*
* Assure that we only show the context menu item on items we intend:
* - Project references
* - NuGet package references
*/
if ((AssemblyReferenceForILSpy.Detect(selectedItemData) != null)
|| (ProjectReferenceForILSpy.Detect(selectedItemData) != null)
|| (NuGetReferenceForILSpy.Detect(selectedItemData) != null)) {
menuItem.Visible = true;
}
}
}
protected override void OnExecute(object sender, EventArgs e)
{
var explorer = owner.DTE.ToolWindows.SolutionExplorer;
var item = ((object[])explorer.SelectedItems).FirstOrDefault() as UIHierarchyItem;
var itemObject = owner.GetSelectedItemsData<object>().FirstOrDefault();
if (itemObject == null)
return;
if (item == null) return;
if (item.Object is Reference reference) {
var referenceItem = AssemblyReferenceForILSpy.Detect(itemObject);
if (referenceItem != null) {
Reference reference = itemObject as Reference;
var project = reference.ContainingProject;
var roslynProject = owner.Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == project.FileName);
var references = GetReferences(roslynProject);
var parameters = referenceItem.GetILSpyParameters(references);
if (references.TryGetValue(reference.Name, out var path))
OpenAssembliesInILSpy(new[] { path });
OpenAssembliesInILSpy(parameters);
else
owner.ShowMessage("Could not find reference '{0}', please ensure the project and all references were built correctly!", reference.Name);
} else {
dynamic referenceObject = item.Object;
if (TryGetProjectFileName(referenceObject, out string fileName)) {
var roslynProject = owner.Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == fileName);
var references = GetReferences(roslynProject);
if (references.TryGetValue(referenceObject.Name, out string path)) {
OpenAssembliesInILSpy(new[] { path });
return;
}
} else {
var values = GetProperties(referenceObject.Properties, "Type", "FusionName", "ResolvedPath");
if (values[0] == "Package") {
values = GetProperties(referenceObject.Properties, "Name", "Version", "Path");
if (values[0] != null && values[1] != null && values[2] != null) {
OpenAssembliesInILSpy(new[] { $"{values[2]}\\{values[0]}.{values[1]}.nupkg" });
return;
}
} else if (values[2] != null) {
OpenAssembliesInILSpy(new[] { $"{values[2]}" });
return;
} else if (!string.IsNullOrWhiteSpace(values[1])) {
OpenAssembliesInILSpy(new string[] { GacInterop.FindAssemblyInNetGac(AssemblyNameReference.Parse(values[1])) });
return;
}
}
owner.ShowMessage("Could not find reference '{0}', please ensure the project and all references were built correctly!", referenceObject.Name);
}
}
private bool TryGetProjectFileName(dynamic referenceObject, out string fileName)
{
try {
fileName = referenceObject.Project.FileName;
return true;
} catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) {
fileName = null;
return false;
// Handle NuGet references
var nugetRefItem = NuGetReferenceForILSpy.Detect(itemObject);
if (nugetRefItem != null) {
OpenAssembliesInILSpy(nugetRefItem.GetILSpyParameters());
}
}
private string[] GetProperties(Properties properties, params string[] names)
{
string[] values = new string[names.Length];
foreach (dynamic p in properties) {
try {
for (int i = 0; i < names.Length; i++) {
if (names[i] == p.Name) {
values[i] = p.Value;
break;
}
}
} catch {
continue;
}
}
return values;
}
private object GetPropertyObject(EnvDTE.Properties properties, string name)
{
foreach (dynamic p in properties) {
try {
if (name == p.Name) {
return p.Object;
// Handle project references
var projectRefItem = ProjectReferenceForILSpy.Detect(itemObject);
if (projectRefItem != null) {
var projectItem = itemObject as ProjectItem;
string fileName = projectItem.ContainingProject?.FileName;
if (!string.IsNullOrEmpty(fileName)) {
var roslynProject = owner.Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == fileName);
var references = GetReferences(roslynProject);
if (references.TryGetValue(projectItem.Name, out string path)) {
OpenAssembliesInILSpy(projectRefItem.GetILSpyParameters(references));
return;
}
} catch {
continue;
}
}
return null;
}
private bool HasProperties(EnvDTE.Properties properties, params string[] names)
{
return properties.Count > 0 && names.Any(n => HasProperty(properties, n));
}
private bool HasProperty(EnvDTE.Properties properties, string name)
{
foreach (dynamic p in properties) {
try {
if (name == p.Name) {
return true;
}
} catch {
continue;
}
OpenAssembliesInILSpy(projectRefItem.GetILSpyParameters());
return;
}
return false;
}
internal static void Register(ILSpyAddInPackage owner)
@ -127,4 +89,5 @@ namespace ICSharpCode.ILSpy.AddIn.Commands @@ -127,4 +89,5 @@ namespace ICSharpCode.ILSpy.AddIn.Commands
instance = new OpenReferenceCommand(owner);
}
}
}

54
ILSpy.AddIn/Commands/ProjectItemForILSpy.cs

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EnvDTE;
namespace ICSharpCode.ILSpy.AddIn.Commands
{
/// <summary>
/// Represents a project item in Solution Explorer, which can be opened in ILSpy.
/// </summary>
class ProjectItemForILSpy
{
SelectedItem item;
ProjectItemForILSpy(SelectedItem item)
{
this.item = item;
}
/// <summary>
/// Detects whether the given <see cref="SelectedItem"/> represents a supported project.
/// </summary>
/// <param name="item">Selected item to check.</param>
/// <returns><see cref="ProjectItemForILSpy"/> instance or <c>null</c>, if item is not a supported project.</returns>
public static ProjectItemForILSpy Detect(SelectedItem item)
{
return new ProjectItemForILSpy(item);
}
/// <summary>
/// If possible retrieves parameters to use for launching ILSpy instance.
/// </summary>
/// <param name="owner">Package instance.</param>
/// <returns>Parameters object or <c>null, if not applicable.</c></returns>
public ILSpyParameters GetILSpyParameters(ILSpyAddInPackage owner)
{
var project = item.Project;
var roslynProject = owner.Workspace.CurrentSolution.Projects.FirstOrDefault(p => p.FilePath == project.FileName);
string outputFileName = Path.GetFileName(roslynProject.OutputFilePath);
// Get the directory path based on the project file.
string projectPath = Path.GetDirectoryName(project.FullName);
// Get the output path based on the active configuration
string projectOutputPath = project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString();
// Combine the project path and output path to get the bin path
return new ILSpyParameters(new[] { Path.Combine(projectPath, projectOutputPath, outputFileName) });
}
}
}

78
ILSpy.AddIn/Commands/ProjectReferenceForILSpy.cs

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EnvDTE;
using Mono.Cecil;
namespace ICSharpCode.ILSpy.AddIn.Commands
{
/// <summary>
/// Represents a project reference item in Solution Explorer, which can be opened in ILSpy.
/// </summary>
class ProjectReferenceForILSpy
{
ProjectItem projectItem;
string fusionName;
string resolvedPath;
ProjectReferenceForILSpy(ProjectItem projectItem, string fusionName, string resolvedPath)
{
this.projectItem = projectItem;
this.fusionName = fusionName;
this.resolvedPath = resolvedPath;
}
/// <summary>
/// Detects whether the given selected item represents a supported project.
/// </summary>
/// <param name="itemData">Data object of selected item to check.</param>
/// <returns><see cref="ProjectReferenceForILSpy"/> instance or <c>null</c>, if item is not a supported project.</returns>
public static ProjectReferenceForILSpy Detect(object itemData)
{
if (itemData is ProjectItem projectItem) {
var properties = Utils.GetProperties(projectItem.Properties, "FusionName", "ResolvedPath");
string fusionName = properties[0] as string;
string resolvedPath = properties[1] as string;
if ((fusionName != null) || (resolvedPath != null)) {
return new ProjectReferenceForILSpy(projectItem, fusionName, resolvedPath);
}
}
return null;
}
/// <summary>
/// If possible retrieves parameters to use for launching ILSpy instance.
/// </summary>
/// <param name="projectReferences">List of current project's references.</param>
/// <returns>Parameters object or <c>null, if not applicable.</c></returns>
public ILSpyParameters GetILSpyParameters(Dictionary<string, string> projectReferences)
{
string fileName = projectItem.ContainingProject?.FileName;
if (!string.IsNullOrEmpty(fileName)) {
if (projectReferences.TryGetValue(projectItem.Name, out string path)) {
return new ILSpyParameters(new[] { path });
}
}
return null;
}
/// <summary>
/// If possible retrieves parameters to use for launching ILSpy instance.
/// </summary>
/// <returns>Parameters object or <c>null, if not applicable.</c></returns>
public ILSpyParameters GetILSpyParameters()
{
if (resolvedPath != null) {
return new ILSpyParameters(new[] { $"{resolvedPath}" });
} else if (!string.IsNullOrWhiteSpace(fusionName)) {
return new ILSpyParameters(new string[] { GacInterop.FindAssemblyInNetGac(AssemblyNameReference.Parse(fusionName)) });
}
return null;
}
}
}

5
ILSpy.AddIn/ILSpy.AddIn.csproj

@ -63,6 +63,10 @@ @@ -63,6 +63,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\AssemblyReferenceForILSpy.cs" />
<Compile Include="Commands\NuGetReferenceForILSpy.cs" />
<Compile Include="Commands\ProjectItemForILSpy.cs" />
<Compile Include="Commands\ProjectReferenceForILSpy.cs" />
<Compile Include="Commands\OpenCodeItemCommand.cs" />
<Compile Include="Commands\OpenILSpyCommand.cs" />
<Compile Include="Commands\OpenProjectOutputCommand.cs" />
@ -100,6 +104,7 @@ @@ -100,6 +104,7 @@
</ItemGroup>
<ItemGroup>
<None Include="ILSpyAddIn.vsct" />
<None Include="source.extension.vsixmanifest">
<SubType>Designer</SubType>
</None>

21
ILSpy.AddIn/ILSpyAddIn.vsct

@ -51,6 +51,10 @@ @@ -51,6 +51,10 @@
<Group guid="guidILSpyAddInCmdSet" id="OpenILSpyCodeItemGroup" priority="0x0200">
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
</Group>
<Group guid="guidILSpyAddInCmdSet" id="OpenILSpyRefGroup" priority="0x0200">
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_REFERENCE"/>
</Group>
</Groups>
<!--Buttons section. -->
@ -83,13 +87,13 @@ @@ -83,13 +87,13 @@
</Strings>
</Button>
<Button guid="guidILSpyAddInCmdSet" id="cmdidOpenCodeItemInILSpy" priority="0x0600" type="Button">
<!--<Button guid="guidILSpyAddInCmdSet" id="cmdidOpenCodeItemInILSpy" priority="0x0600" type="Button">
<Parent guid="guidILSpyAddInCmdSet" id="OpenILSpyCodeItemGroup" />
<Icon guid="guidImages" id="bmpLogo" />
<Strings>
<ButtonText>Open code in ILSpy</ButtonText>
</Strings>
</Button>
</Button>-->
<Button guid="guidILSpyAddInCmdSet" id="cmdidOpenILSpy" priority="0x0600" type="Button">
<Parent guid="guidILSpyAddInCmdSet" id="OpenILSpyGroup" />
@ -112,19 +116,6 @@ @@ -112,19 +116,6 @@
</Bitmaps>
</Commands>
<CommandPlacements>
<!--
<CommandPlacement guid="guidILSpyAddInCmdSet" id="cmdidOpenReferenceInILSpy" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDG_VS_CTXT_REFERENCE"/>
</CommandPlacement>
-->
<!-- HACK : since there is no special ID for the new .NET Standard 2.0 reference nodes,
use the ITEM_OPEN id and hide the item if not applicable. -->
<CommandPlacement guid="guidILSpyAddInCmdSet" id="cmdidOpenReferenceInILSpy" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDG_VS_CTXT_ITEM_OPEN"/>
</CommandPlacement>
</CommandPlacements>
<Symbols>
<!-- This is the package guid. -->
<GuidSymbol name="guidILSpyAddInPkg" value="{a9120dbe-164a-4891-842f-fb7829273838}" />

12
ILSpy.AddIn/ILSpyAddInPackage.cs

@ -13,6 +13,7 @@ using System.Linq; @@ -13,6 +13,7 @@ using System.Linq;
using ICSharpCode.ILSpy.AddIn.Commands;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices;
using EnvDTE;
namespace ICSharpCode.ILSpy.AddIn
{
@ -109,5 +110,16 @@ namespace ICSharpCode.ILSpy.AddIn @@ -109,5 +110,16 @@ namespace ICSharpCode.ILSpy.AddIn
)
);
}
public IEnumerable<T> GetSelectedItemsData<T>()
{
if (DTE.ToolWindows.SolutionExplorer.SelectedItems is IEnumerable<UIHierarchyItem> hierarchyItems) {
foreach (var item in hierarchyItems) {
if (item.Object is T typedItem) {
yield return typedItem;
}
}
}
}
}
}

50
ILSpy.AddIn/Utils.cs

@ -1,7 +1,10 @@ @@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using EnvDTE;
namespace ICSharpCode.ILSpy.AddIn
{
@ -111,5 +114,52 @@ namespace ICSharpCode.ILSpy.AddIn @@ -111,5 +114,52 @@ namespace ICSharpCode.ILSpy.AddIn
}
return result;
}
public static bool TryGetProjectFileName(dynamic referenceObject, out string fileName)
{
try {
fileName = referenceObject.Project.FileName;
return true;
} catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException) {
fileName = null;
return false;
}
}
public static object[] GetProperties(Properties properties, params string[] names)
{
var values = new object[names.Length];
foreach (object p in properties) {
try {
if (p is Property property) {
for (int i = 0; i < names.Length; i++) {
if (names[i] == property.Name) {
values[i] = property.Value;
break;
}
}
}
} catch {
continue;
}
}
return values;
}
public static List<(string, object)> GetAllProperties(Properties properties)
{
var result = new List<(string, object)>();
for (int i = 0; i < properties.Count; i++) {
try {
if (properties.Item(i) is Property p) {
result.Add((p.Name, p.Value));
}
} catch {
continue;
}
}
return result;
}
}
}

Loading…
Cancel
Save