Skip to content

Instantly share code, notes, and snippets.

@teramako
Last active July 14, 2024 04:52
Show Gist options
  • Save teramako/fb2b00e50cdfc56a7ca3652124e951c3 to your computer and use it in GitHub Desktop.
Save teramako/fb2b00e50cdfc56a7ca3652124e951c3 to your computer and use it in GitHub Desktop.
DLLライブラリ内のクラスメンバーやEnum値を列挙する。
<#
.SYNOPSIS
DLL 内の Class および Enum メンバーを列挙する
.DESCRIPTION
対象DLLを読み込み、Class や Enum メンバーと、
その属性(`System.ComponentModel.DescriptionAttribute`)値を列挙します。
.PARAMETER Path
対象DLLファイル
.PARAMETER Html
HTML (Tableタグ) 出力する。
h1 (namespace), h2 (class/enum), h3 (Method, Property, Field) の見出しも付与されます。
#>
using namespace System.Reflection;
using namespace System.Collections.Generic;
using namespace System.ComponentModel;
[OutputType("MemberItem")]
param(
[Parameter(Mandatory, Position=0)]
[string] $Path
,
[switch] $Html
)
class MemberItem {
hidden [type] $Parent
[string] $Namespace
[string] $ClassName
[string] $Kind # "Enum", Method, Property or Field
[string] $Type # "Enum", ReturnType or PropertyType
[string] $Name
[string] $Description # ComponentMode.DescriptionAttribute
MemberItem([type] $type, [MemberInfo] $memberInfo) {
$this.Parent = $type
$this.Namespace = $type.Namespace;
$this.ClassName = $type.Name;
$this.Kind = $memberInfo.MemberType;
$this.Description = [MemberItem]::GetDescription($memberInfo);
switch ($memberInfo.MemberType) {
Method {
$method = [MethodInfo]$memberInfo
$params = $method.GetParameters() | ForEach-Object { "{0} {1}" -F [MemberItem]::GetTypeName($_.ParameterType), $_.Name }
$this.Name = "{0}({1})" -f $method.Name, ($params -join ", ")
$this.Type = [MemberItem]::GetTypeName($method.ReturnType)
}
Property {
$prop = [PropertyInfo]$memberInfo
$this.Name = $prop.Name;
$this.Type = [MemberItem]::GetTypeName($prop.PropertyType)
}
Field {
$field = [FieldInfo]$memberInfo
$this.Name = $field.Name;
$this.Type = [MemberItem]::GetTypeName($field.FieldType)
}
}
}
MemberItem([type] $type, [enum] $value) {
$this.Parent = $type;
$this.Namespace = $type.Namespace;
$this.ClassName = $type.Name;
$this.Kind = "Enum"
$this.Type = "Enum"
$this.Name = "{0:g} (0x{0:x})" -f $value;
$this.Description = [MemberItem]::GetDescription($value.GetType().GetField($value.ToString()));
}
static [string] GetDescription([MemberInfo] $memberInfo) {
return $memberInfo.GetCustomAttributes([DescriptionAttribute]).Description
}
static [string] GetTypeName([type] $Type) {
$typeName = $Type.ToString();
$i = $typeName.IndexOf("``")
if ($i -gt 0) {
$typeName = $typeName.Substring(0, $i)
$genericParams = $Type.GenericTypeArguments | ForEach-Object { [MemberItem]::GetTypeName($_) }
$typeName += ("<{0}>" -F ($genericParams -join ", "))
}
return $typeName.Substring($Type.Namespace.Length + 1);
}
}
function Get-CustomMember {
param(
[Parameter(Mandatory, Position=0)]
[string] $Path
)
begin {
$libFile = Get-Item -LiteralPath $Path;
$lib = [Assembly]::LoadFile($libFile.FullName);
$flags = [BindingFlags]::Public -bor [BindingFlags]::Instance
}
end {
foreach ($type in $lib.GetTypes()) {
if (-not $type.IsPublic) { continue; }
if ($type.IsClass) {
foreach ($member in $type.GetMembers($flags)) {
# 別モジュールから継承して得られたメンバーは除外
if ($member.Module -ne $lib.ManifestModule) { continue; }
# Getter/Setter の場合に true となると思われる IsSpecialName も除外
if ($member.IsSpecialName) { continue; }
Write-Output ([MemberItem]::new($type, $member))
}
} elseif ($type.IsEnum) {
foreach ($value in $type.GetEnumValues()) {
# Write-Output ([EnumInfo]::new($type.Namespace, $type.Name, $value))
Write-Output ([MemberItem]::new($type, $value))
}
}
}
}
}
if ($Html) {
Get-CustomMember -Path $Path | Group-Object Namespace | ForEach-Object {
$nsName = $_.Name;
"<h1 id=`"namespace-{0}`">{0}</h1>" -F $nsName;
$_.Group | Group-Object ClassName | ForEach-Object {
$className = $_.Name;
$desc = [MemberItem]::GetDescription($_.Group[0].Parent)
if ($_.Group[0].Kind -eq "Enum") {
"<h2 id=`"{0}.{1}`">Enum: {1}</h2>" -F $nsName, $className
if ($desc) { "<p>{0}</p>" -F $desc }
$_.Group | ConvertTo-Html -Fragment -Property Name,Description
} else {
"<h2 id=`"{0}.{1}`">Class: {1}</h2>" -F $nsName, $className
if ($desc) { "<p>{0}</p>" -F $desc }
$_.Group | Group-Object Kind | ForEach-Object {
"<h3 id=`"{0}.{1}-{2}`">{2}</h2>" -F $nsName, $ClassName, $_.Name;
$_.Group | ConvertTo-Html -Fragment -Property Type,Name,Description
}
}
}
}
} else {
Get-CustomMember -Path $Path
}

Samples

Code in DLL

using System;
using System.ComponentModel;

namespace teramako.test
{
    [Description("Test Class")]
    public class TestClass
    {
        [Description("ID 番号")]
        public int Id { get; private set; } = 0;
        [Description("氏名")]
        public string Name { get; private set; } = "";
        [Description("メールアドレス")]
        public string Email { get; private set; } = "";

        [Description(@"このフィールドは
                よくわからないけど、Null かもしれない。
                ")]
        public int? Age = null;

        [Description("Test Method")]
        public bool SetAge(int? age) {
            if (age < 0) return false;
            Age = age;
            return true;
        }
    }

    [Flags()]
    [Description("Enum のテスト")]
    public enum TestEnum {
        [Description("なにもなし")]
        None = 0,
        [Description("状態 A")]
        A    = 1 << 0,
        B    = 1 << 1,
    }

1. List up

$ .\Get-MemberDescription.ps1 ./TestLib/bin/Debug/net8.0/TestLib.dll | Format-Table

Namespace     ClassName Kind     Type            Name                        Description
---------     --------- ----     ----            ----                        -----------
teramako.test TestClass Method   Boolean         SetAge(Nullable<Int32> age) Test Method
teramako.test TestClass Property Int32           Id                          ID 番号
teramako.test TestClass Property String          Name                        氏名
teramako.test TestClass Property String          Email                       メールアドレス
teramako.test TestClass Field    Nullable<Int32> Age                         このフィールドは…
teramako.test TestEnum  Enum     Enum            None (0x00000000)           なにもなし
teramako.test TestEnum  Enum     Enum            A (0x00000001)              状態 A
teramako.test TestEnum  Enum     Enum            B (0x00000002)

2. HTML

$ .\Get-MemberDescription.ps1 ./TestLib/bin/Debug/net8.0/TestLib.dll -Html

<h1 id="namespace-teramako.test">teramako.test</h1>
<h2 id="teramako.test.TestClass">Class: TestClass</h2>
<p>Test Class</p>
<h3 id="teramako.test.TestClass-Field">Field</h2>
<table>
<colgroup><col/><col/><col/></colgroup>
<tr><th>Type</th><th>Name</th><th>Description</th></tr>
<tr><td>Nullable&lt;Int32&gt;</td><td>Age</td><td>このフィールドは
                よくわからないけど、Null かもしれない。
                </td></tr>
</table>
<h3 id="teramako.test.TestClass-Method">Method</h2>
<table>
<colgroup><col/><col/><col/></colgroup>
<tr><th>Type</th><th>Name</th><th>Description</th></tr>
<tr><td>Boolean</td><td>SetAge(Nullable&lt;Int32&gt; age)</td><td>Test Method</td></tr>
</table>
<h3 id="teramako.test.TestClass-Property">Property</h2>
<table>
<colgroup><col/><col/><col/></colgroup>
<tr><th>Type</th><th>Name</th><th>Description</th></tr>
<tr><td>Int32</td><td>Id</td><td>ID 番号</td></tr>
<tr><td>String</td><td>Name</td><td>氏名</td></tr>
<tr><td>String</td><td>Email</td><td>メールアドレス</td></tr>
</table>
<h2 id="teramako.test.TestEnum">Enum: TestEnum</h2>
<p>Enum のテスト</p>
<table>
<colgroup><col/><col/></colgroup>
<tr><th>Name</th><th>Description</th></tr>
<tr><td>None (0x00000000)</td><td>なにもなし</td></tr>
<tr><td>A (0x00000001)</td><td>状態 A</td></tr>
<tr><td>B (0x00000002)</td><td></td></tr>
</table>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment