1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VB.NET 4.8.1を今の時代に触る!!PHP 8.x, TypeScript 5.xとの言語仕様比較(第14章 属性とメタデータ)

1
Last updated at Posted at 2025-12-27

目次
第13章 モジュールと名前空間

第14章 属性とメタデータ

14.1 概要

言語 機能名 構文
PHP Attributes #[AttributeName]
TypeScript Decorators @decoratorName
VB.NET Attributes <AttributeName>

14.2 PHP Attributes(PHP 8.0+)

基本的な使用法

<?php
// 組み込み属性
#[Deprecated("Use newMethod instead")]
function oldMethod(): void {}

// クラスに属性を付与
#[Entity]
#[Table(name: "users")]
class User {
    #[Column(type: "integer", primary: true)]
    public int $id;

    #[Column(type: "string", length: 255)]
    public string $name;

    #[Route("/users/{id}", methods: ["GET"])]
    public function show(int $id): Response {}
}

カスタム属性の定義

<?php
#[Attribute(Attribute::TARGET_CLASS)]
class Entity {
    public function __construct(
        public ?string $table = null
    ) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Column {
    public function __construct(
        public string $type,
        public int $length = 255,
        public bool $primary = false
    ) {}
}

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Route {
    public function __construct(
        public string $path,
        public array $methods = ["GET"]
    ) {}
}

属性の読み取り

<?php
$reflection = new ReflectionClass(User::class);

// クラス属性
$attributes = $reflection->getAttributes(Entity::class);
foreach ($attributes as $attribute) {
    $instance = $attribute->newInstance();
    echo $instance->table;
}

// プロパティ属性
foreach ($reflection->getProperties() as $property) {
    $attrs = $property->getAttributes(Column::class);
    foreach ($attrs as $attr) {
        $column = $attr->newInstance();
        echo $column->type;
    }
}

// メソッド属性
foreach ($reflection->getMethods() as $method) {
    $routes = $method->getAttributes(Route::class);
    foreach ($routes as $route) {
        $instance = $route->newInstance();
        echo $instance->path;
    }
}

14.3 TypeScript Decorators

基本的な使用法(実験的機能)

// tsconfig.json で有効化
// "experimentalDecorators": true

// クラスデコレータ
@sealed
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    @readonly
    greet() {
        return "Hello, " + this.greeting;
    }
}

// プロパティデコレータ
class User {
    @required
    name: string = "";

    @range(0, 150)
    age: number = 0;
}

// パラメータデコレータ
class UserService {
    greet(@validate name: string): string {
        return `Hello, ${name}`;
    }
}

デコレータの定義

// クラスデコレータ
function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

// デコレータファクトリ
function entity(tableName: string) {
    return function <T extends { new (...args: any[]): {} }>(constructor: T) {
        return class extends constructor {
            tableName = tableName;
        };
    };
}

// メソッドデコレータ
function readonly(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    descriptor.writable = false;
    return descriptor;
}

// プロパティデコレータ
function required(target: any, propertyKey: string) {
    // メタデータに記録
    Reflect.defineMetadata("required", true, target, propertyKey);
}

// パラメータデコレータ
function validate(
    target: Object,
    propertyKey: string | symbol,
    parameterIndex: number
) {
    const existingValidations: number[] =
        Reflect.getMetadata("validate", target, propertyKey) || [];
    existingValidations.push(parameterIndex);
    Reflect.defineMetadata("validate", existingValidations, target, propertyKey);
}

// ログデコレータ(実用例)
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Calling ${propertyKey} with`, args);
        const result = originalMethod.apply(this, args);
        console.log(`Result:`, result);
        return result;
    };
    return descriptor;
}

TC39 Stage 3 Decorators(新構文)

// 新しいデコレータ構文(TypeScript 5.0+)
// tsconfig.json で "experimentalDecorators": false

function logged<This, Args extends any[], Return>(
    target: (this: This, ...args: Args) => Return,
    context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
    const methodName = String(context.name);
    return function (this: This, ...args: Args): Return {
        console.log(`Calling ${methodName}`);
        return target.call(this, ...args);
    };
}

class Example {
    @logged
    greet(name: string) {
        return `Hello, ${name}`;
    }
}

14.4 VB.NET Attributes

基本的な使用法

' 組み込み属性
<Obsolete("Use NewMethod instead")>
Public Sub OldMethod()
End Sub

' 複数の属性
<Serializable>
<DataContract>
Public Class User
    <DataMember>
    <Required>
    Public Property Name As String

    <DataMember>
    <Range(0, 150)>
    Public Property Age As Integer
End Class

' 属性のパラメータ
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Public Shared Function MessageBox(hWnd As IntPtr,
                                   text As String,
                                   caption As String,
                                   type As UInteger) As Integer
End Function

カスタム属性の定義

' 属性クラス
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Struct)>
Public Class EntityAttribute
    Inherits Attribute

    Public Property TableName As String

    Public Sub New()
    End Sub

    Public Sub New(tableName As String)
        Me.TableName = tableName
    End Sub
End Class

<AttributeUsage(AttributeTargets.Property, AllowMultiple:=False)>
Public Class ColumnAttribute
    Inherits Attribute

    Public Property Name As String
    Public Property Type As String
    Public Property Length As Integer = 255
    Public Property IsPrimaryKey As Boolean = False

    Public Sub New(name As String)
        Me.Name = name
    End Sub
End Class

<AttributeUsage(AttributeTargets.Method, AllowMultiple:=True)>
Public Class RouteAttribute
    Inherits Attribute

    Public Property Path As String
    Public Property Methods As String() = {"GET"}

    Public Sub New(path As String)
        Me.Path = path
    End Sub
End Class

' 使用
<Entity("users")>
Public Class User
    <Column("user_id", IsPrimaryKey:=True)>
    Public Property Id As Integer

    <Column("user_name", Length:=100)>
    Public Property Name As String
End Class

属性の読み取り

Dim type = GetType(User)

' クラス属性
Dim entityAttr = type.GetCustomAttribute(Of EntityAttribute)()
If entityAttr IsNot Nothing Then
    Console.WriteLine($"Table: {entityAttr.TableName}")
End If

' プロパティ属性
For Each prop In type.GetProperties()
    Dim columnAttr = prop.GetCustomAttribute(Of ColumnAttribute)()
    If columnAttr IsNot Nothing Then
        Console.WriteLine($"Column: {columnAttr.Name}, Type: {columnAttr.Type}")
    End If
Next

' すべての属性を取得
Dim allAttributes = type.GetCustomAttributes(True)
For Each attr In allAttributes
    Console.WriteLine(attr.GetType().Name)
Next

' 属性の存在確認
If Attribute.IsDefined(type, GetType(SerializableAttribute)) Then
    Console.WriteLine("Class is serializable")
End If

よく使用される組み込み属性

属性 用途
<Obsolete> 非推奨マーク
<Serializable> シリアライズ可能
<DataContract> / <DataMember> WCF/JSON シリアライズ
<Required> 必須フィールド
<Range> 値の範囲検証
<StringLength> 文字列長制限
<RegularExpression> 正規表現検証
<Conditional> 条件付きコンパイル
<DllImport> P/Invoke
<DebuggerDisplay> デバッガ表示
<CallerMemberName> 呼び出し元メンバー名
' 実用例
Public Class ValidationExample
    <Required(ErrorMessage:="Name is required")>
    <StringLength(100, MinimumLength:=1)>
    Public Property Name As String

    <Range(0, 150, ErrorMessage:="Age must be between 0 and 150")>
    Public Property Age As Integer

    <RegularExpression("^[\w\.-]+@[\w\.-]+\.\w+$")>
    Public Property Email As String
End Class

' デバッガ表示
<DebuggerDisplay("User: {Name}, Age: {Age}")>
Public Class DebugUser
    Public Property Name As String
    Public Property Age As Integer
End Class

' 条件付きコンパイル
Public Class Logger
    <Conditional("DEBUG")>
    Public Shared Sub DebugLog(message As String)
        Console.WriteLine($"[DEBUG] {message}")
    End Sub
End Class

' 呼び出し元情報
Public Sub Log(message As String,
               <CallerMemberName> Optional memberName As String = "",
               <CallerFilePath> Optional filePath As String = "",
               <CallerLineNumber> Optional lineNumber As Integer = 0)
    Console.WriteLine($"{filePath}:{lineNumber} [{memberName}] {message}")
End Sub

14.5 メタプログラミングの比較

機能 PHP TypeScript VB.NET
実行時の型情報 Reflection API 制限的(実行時に消える) Reflection API
コンパイル時処理 なし デコレータ なし
コード生成 eval、include なし Reflection.Emit、Source Generators
動的メソッド呼び出し $obj->$method() obj[method]() CallByName、Reflection

第15章 列挙型

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?