iconDreamine
← 목록

Dreamine.MVVM.Generators

stablev1.0.13

Source Generator — [AutoRegister], [AutoNotify] 어트리뷰트 기반 코드 자동 생성.

#analyzer#dreamine#mvvm#roslyn#source-generator
TFM netstandard2.0Package Dreamine.MVVM.Generators참조 Dreamine.MVVM.Attributes

Dreamine.MVVM.Generators

Dreamine.MVVM.Generators는 Dreamine MVVM 생태계에서 사용하는 Roslyn 증분 소스 제너레이터 패키지입니다.

이 패키지는 Attribute로 선언한 의도를 기반으로 MVVM 보일러플레이트 코드를 컴파일 타임에 생성합니다.

현재 기준으로 다루는 주요 Attribute는 다음과 같습니다.

  • DreamineProperty
  • DreamineEntry
  • DreamineModel
  • DreamineEvent
  • DreamineCommand

이 패키지의 목표는 반복 코드를 줄이되, 생성 규칙과 제약을 명시적으로 유지하는 것입니다.

➡️ English Documentation


이 패키지가 하는 일

MVVM 프로젝트에서는 반복적으로 다음 코드가 필요합니다.

  • backing field → property 노출
  • method → ICommand 프로퍼티 생성
  • model / event 참조 노출
  • 앱 엔트리 부트스트랩 코드 생성
  • 선언형 command forwarding 코드 생성

Dreamine.MVVM.Generators는 이 반복 코드를 생성 계층으로 이동시켜 ViewModel 및 App 코드 양을 줄입니다.


주요 특징

  • Roslyn Incremental Source Generator 기반
  • Analyzer 패키지 형태로 배포 가능
  • Dreamine Attribute를 기준으로 코드 생성
  • 엔트리 부트스트랩 코드 생성 지원
  • 필드 기반 Auto Wiring 지원
  • DreamineCommand 기반 직접 실행/forwarding command 생성 지원
  • analyzers/dotnet/cs 경로로 패키징 가능
  • buildTransitive 기반 자동 analyzer 등록 지원

요구 사항

  • 대상 프레임워크: netstandard2.0
  • 일반적으로 함께 사용되는 패키지:
    • Dreamine.MVVM.Attributes
    • Dreamine.MVVM.Core
    • WPF / .NET MVVM 애플리케이션

설치

방법 A) NuGet

dotnet add package Dreamine.MVVM.Generators

방법 B) PackageReference

<ItemGroup>
  <PackageReference Include="Dreamine.MVVM.Generators" Version="1.0.6" />
</ItemGroup>

이 패키지는 Analyzer 패키지 형태로 사용되며, buildTransitive를 통해 소비 프로젝트에 자동 등록되는 구조를 권장합니다.


프로젝트 구조

Dreamine.MVVM.Generators
├── DreamineAutoWiringGenerator.cs
├── DreamineCommandSourceGenerator.cs
├── DreamineEntryGenerator.cs
├── AnalyzerReleases.Shipped.md
├── AnalyzerReleases.Unshipped.md
├── buildTransitive/
│   └── Dreamine.MVVM.Generators.targets
└── Dreamine.MVVM.Generators.csproj

아키텍처 역할

이 패키지는 Dreamine MVVM 스택의 생성 계층에 속합니다.

ViewModel / App Source Code
        │
        ├─ Dreamine.MVVM.Attributes
        │     (markers / metadata)
        │
        ├─ Dreamine.MVVM.Generators
        │     (compile-time code generation)
        │
        └─ Dreamine.MVVM.Core
              (runtime MVVM infrastructure)

책임 분리는 다음과 같습니다.

  • Attributes: 의도 선언
  • Generators: 코드 생성
  • Core: 런타임 동작 수행

지원 Generator

1) DreamineEntryGenerator

[DreamineEntry]가 적용된 타입을 기준으로 애플리케이션 부트스트랩 코드를 생성합니다.

현재 역할

  • 앱 시작 시 초기화 코드 생성
  • DMContainer.AutoRegisterAll(...)
  • ViewModelLocator.RegisterAll(...)
  • FrameworkElement.Loaded 이벤트 기반 View ↔ ViewModel 자동 연결
  • RegisterBefore, RegisterAfter, ShowMainWindow partial hook 생성

현재 제약

  • 대상 타입은 partial 이어야 함
  • 대상 타입은 System.Windows.Application을 상속해야 함
  • 유효한 엔트리 타입은 하나만 허용하는 방향을 전제로 함

예시

using Dreamine.MVVM.Attributes;

[DreamineEntry]
public partial class App : Application
{
}

2) DreamineAutoWiringGenerator

[DreamineProperty], [DreamineModel], [DreamineEvent]가 적용된 필드를 기준으로 보조 프로퍼티를 생성합니다.

현재 역할

  • _titleTitle
  • _modelModel
  • _eventEvent

현재 기준

  • 필드 기반 생성만 처리
  • 속성(Property) 선언 자체를 다시 생성 대상으로 보지 않음
  • partial class에 보조 프로퍼티를 추가 생성
  • 기존 멤버와 이름 충돌 시 생성 생략

예시

using Dreamine.MVVM.Attributes;

public partial class MainViewModel
{
    [DreamineProperty]
    private string _title;

    [DreamineModel]
    private MainModel _model;

    [DreamineEvent]
    private MainEvent _event;
}

생성 의도

  • Title → field-backed property
  • Model → model access property
  • Event → event access property

주의 사항

  • [DreamineProperty] 생성 코드는 SetProperty(ref field, value) 사용을 전제로 함
    즉 대상 타입에 SetProperty가 존재해야 함
  • [DreamineModel], [DreamineEvent]는 현재 생성 정책상 readonly 필드 사용을 권장하지 않음
  • DreamineModelnew T() 초기화 경로를 사용함
  • DreamineEventDMContainer.Resolve<T>() 초기화 경로를 사용함
  • 생성 코드에서 더 이상 ViewModelBase 상속을 강제하지 않음

3) DreamineCommandSourceGenerator

[DreamineCommand]가 적용된 메서드를 기준으로 ICommand 프로퍼티를 생성합니다.

현재 역할

  • {MethodName}Command 프로퍼티 생성
  • 필요 시 CommandName override 지원
  • TargetMethod가 없으면 주석이 붙은 메서드를 직접 실행
  • forwarding이 필요한 경우 TargetMethod 호출 코드 생성
  • forwarding 결과값이 있으면 BindTo 프로퍼티에 대입
  • TargetMethod가 지정되고 메서드 본문이 없을 때 forwarding body 생성
  • 외부 RelayCommand 타입에 직접 의존하지 않도록 생성 파일 내부에 전용 ICommand 구현을 포함하는 방향 사용

예시

using Dreamine.MVVM.Attributes;

public partial class MainViewModel
{
    [DreamineCommand]
    private void Save()
    {
    }

    [DreamineCommand("Event.ReadmeClick", BindTo = "Readme")]
    partial void LoadReadme();
}

현재 제약

  • containing type은 partial 이어야 함
  • 대상 메서드는 parameterless void 여야 함
  • 본문 없는 forwarding 메서드는 partial 이어야 함
  • 생성될 command property 이름이 기존 멤버와 충돌하면 생성하지 않음

빠른 시작

1) 필요한 패키지 추가

<ItemGroup>
  <PackageReference Include="Dreamine.MVVM.Attributes" Version="1.0.6" />
  <PackageReference Include="Dreamine.MVVM.Core" Version="1.0.9" />
  <PackageReference Include="Dreamine.MVVM.Generators" Version="1.0.11" PrivateAssets="all" OutputItemType="Analyzer" />
</ItemGroup>

2) Attribute 선언

using Dreamine.MVVM.Attributes;

public partial class MainViewModel
{
    [DreamineProperty]
    private string _title;

    [DreamineCommand]
    private void Save()
    {
    }

    [DreamineCommand("Event.ReadmeClick", BindTo = "Readme")]
    partial void LoadReadme();
}

3) 빌드

빌드 시 partial source가 생성됩니다.

생성 예:

  • Title property
  • SaveCommand
  • LoadReadmeCommand
  • forwarding method body

현재 코드 기준 중요 메모

1) 생성기는 완전 독립형 런타임 프레임워크가 아니다

생성 코드가 참조하는 런타임 개념은 여전히 존재합니다.

예:

  • DMContainer
  • ViewModelLocator
  • SetProperty

즉 이 패키지는 Dreamine MVVM 스택 안에서 사용하는 것을 전제로 합니다.

2) Attribute별 사용 범위가 동일하지 않다

현재 기준으로:

  • DreamineEntry → App / bootstrap 계층
  • DreaminePropertySetProperty 가능한 ViewModel 계층
  • DreamineModel, DreamineEvent → field access 생성
  • DreamineCommand → method 기반 command 생성

3) 생성 규칙은 점진적으로 엄격해지는 방향이다

현재 Generator 구현은 단순 자동 생성보다 다음을 더 중시합니다.

  • partial 타입 검증
  • 메서드 시그니처 검증
  • 이름 충돌 방지
  • 잘못된 사용에 대한 Diagnostic 추가

Packaging Notes

현재 프로젝트는 Analyzer 패키지 방향으로 구성하는 것을 전제로 합니다.

일반적인 구성 포인트:

  • PackageType=Analyzer
  • OutputItemType=Analyzer
  • IncludeBuildOutput=false
  • generator DLL을 analyzers/dotnet/cs에 패킹
  • buildTransitive를 통한 자동 등록

비교

패키지 역할 런타임 로직 컴파일 타임 생성
Dreamine.MVVM.Attributes 선언 계층 No No
Dreamine.MVVM.Generators 생성 계층 No Yes
Dreamine.MVVM.Core 런타임 계층 Yes No

이 분리는 시스템을 레이어 단위로 유지하기 위한 구조입니다.


권장 조합

이 패키지는 보통 아래와 함께 사용합니다.

Dreamine.MVVM.Attributes
Dreamine.MVVM.Core
Dreamine WPF / UI / App packages

라이선스

MIT License

구조 다이어그램

classDiagram
    class ObservablePropertyGenerator {
        <<SourceGenerator>>
        +Execute(GeneratorExecutionContext) void
        -GenerateProperty(IFieldSymbol) string
    }
    class RelayCommandGenerator {
        <<SourceGenerator>>
        +Execute(GeneratorExecutionContext) void
        -GenerateCommand(IMethodSymbol) string
    }
    class ViewModelGenerator {
        <<SourceGenerator>>
        +Execute(GeneratorExecutionContext) void
        -GeneratePartialClass(INamedTypeSymbol) string
    }
    class ObservablePropertyAttribute {
        +string PropertyName
        +bool NotifyAlso
    }
    class RelayCommandAttribute {
        +string CanExecuteMethod
        +bool IsAsync
    }
    ObservablePropertyGenerator --> ObservablePropertyAttribute
    RelayCommandGenerator --> RelayCommandAttribute

API 문서

타입

AttributeSymbolSet

Attribute 심볼 집합을 나타냅니다.

AutoWiringCandidate

자동 생성 대상 필드 메타데이터를 나타냅니다.

CandidateKind

생성 대상 종류를 나타냅니다.

CommandCandidateModel

생성 대상 메서드 메타데이터를 나타냅니다.

DreamineAutoWiringGenerator

DreamineModelAttribute, DreamineEventAttribute, DreaminePropertyAttribute가 적용된 필드를 기반으로 보조 프로퍼티 코드를 생성하는 증분 생성기입니다.

DreamineCommandSourceGenerator

DreamineCommandAttribute가 적용된 메서드를 기반으로 커맨드 프로퍼티와 forwarding 메서드 구현을 생성하는 증분 생성기입니다.

DreamineEntryGenerator

DreamineEntryAttribute가 적용된 WPF 엔트리 클래스를 분석하고 Dreamine 부트스트랩 코드를 생성하는 증분 생성기입니다.

EntryCandidateModel

\if KO Entry Candidate Model 기능과 관련 상태를 캡슐화합니다. \endif \if EN Encapsulates entry candidate model functionality and related state. \endif

AttributeSymbolSet

#ctor Method

클래스의 새 인스턴스를 초기화합니다.

modelAttribute— Model Attribute 심볼입니다.
eventAttribute— Event Attribute 심볼입니다.
propertyAttribute— Property Attribute 심볼입니다.
EventAttribute Property

Event Attribute 심볼을 가져옵니다.

IsIncomplete Property

필수 심볼이 모두 준비되었는지 여부를 가져옵니다.

ModelAttribute Property

Model Attribute 심볼을 가져옵니다.

PropertyAttribute Property

Property Attribute 심볼을 가져옵니다.

AutoWiringCandidate

#ctor Method

클래스의 새 인스턴스를 초기화합니다.

fieldSymbol— 대상 필드 심볼입니다.
containingType— 필드를 포함하는 타입입니다.
kind— 후보 종류입니다.
generatedPropertyName— 생성할 프로퍼티 이름입니다.
ContainingType Property

필드를 포함하는 타입을 가져옵니다.

FieldSymbol Property

대상 필드 심볼을 가져옵니다.

GeneratedPropertyName Property

생성할 프로퍼티 이름을 가져옵니다.

Kind Property

후보 종류를 가져옵니다.

CommandCandidateModel

#ctor Method

클래스의 새 인스턴스를 초기화합니다.

methodSyntax— 원본 메서드 구문입니다.
methodSymbol— 원본 메서드 심볼입니다.
targetMethod— 대상 메서드 경로입니다.
bindTo— 반환값을 대입할 프로퍼티 이름입니다.
commandPropertyName— 생성할 커맨드 프로퍼티 이름입니다.
canExecuteMethod— 커맨드 실행 가능 여부를 확인할 메서드 이름입니다.
BindTo Property

반환값을 대입할 프로퍼티 이름을 가져옵니다.

CanExecuteMethod Property

CanExecute 판단 메서드 이름을 가져옵니다.

CommandPropertyName Property

생성할 커맨드 프로퍼티 이름을 가져옵니다.

MethodSymbol Property

원본 메서드 심볼을 가져옵니다.

MethodSyntax Property

원본 메서드 구문을 가져옵니다.

TargetMethod Property

대상 메서드 경로를 가져옵니다.

DreamineAutoWiringGenerator

AppendLazyAccessPropertyCode Method

지연 초기화 기반 읽기 전용 프로퍼티 코드를 생성합니다.

builder— 대상 문자열 빌더입니다.
fieldSymbol— 원본 필드 심볼입니다.
typeName— 필드 타입 이름입니다.
fieldName— 필드 이름입니다.
propertyName— 프로퍼티 이름입니다.
initializerExpression— 초기화 식입니다.
AppendPropertyCode Method

종류에 맞는 프로퍼티 코드를 생성합니다.

builder— 대상 문자열 빌더입니다.
kind— 생성 대상 종류입니다.
fieldSymbol— 원본 필드 심볼입니다.
typeName— 필드 타입 이름입니다.
fieldName— 필드 이름입니다.
propertyName— 생성할 프로퍼티 이름입니다.
BuildFileName Method

생성 파일 이름을 만듭니다.

candidate— 대상 후보입니다.

반환: 생성 파일 이름입니다.

BuildSource Method

생성 코드를 만듭니다.

candidate— 생성 대상 후보입니다.

반환: 생성된 C# 소스 문자열입니다.

Emit Method

수집된 후보를 기반으로 소스를 생성합니다.

context— 소스 출력 컨텍스트입니다.
candidates— 수집된 후보 목록입니다.
GetCandidateKind Method

필드에 적용된 Attribute 종류를 판별합니다.

fieldSymbol— 검사할 필드 심볼입니다.
attributeSymbols— 비교할 Attribute 심볼 집합입니다.
matchedAttribute— 일치한 Attribute 데이터입니다.

반환: 일치한 종류가 있으면 해당 를 반환하고, 아니면 을 반환합니다.

GetConfiguredPropertyName Method

Attribute에 지정된 명시적 프로퍼티 이름을 가져옵니다.

attribute— 검사할 Attribute 데이터입니다.

반환: 설정된 이름이 있으면 반환하고, 없으면 을 반환합니다.

HasConflictingMember Method

이미 같은 이름의 멤버가 존재하는지 확인합니다.

typeSymbol— 검사 대상 타입입니다.
memberName— 확인할 멤버 이름입니다.

반환: 같은 이름의 멤버가 있으면 이고, 아니면 입니다.

Initialize Method

증분 생성기 파이프라인을 초기화합니다.

context— 생성기 초기화 컨텍스트입니다.
IsCandidateSyntax Method

후보가 될 수 있는 구문인지 확인합니다.

node— 검사할 구문 노드입니다.

반환: 후보 구문이면 이고, 아니면 입니다.

ResolveGeneratedPropertyName Method

생성할 프로퍼티 이름을 결정합니다.

fieldName— 원본 필드 이름입니다.
configuredPropertyName— 명시적으로 지정된 프로퍼티 이름입니다.

반환: 유효한 프로퍼티 이름이면 반환하고, 아니면 을 반환합니다.

Sanitize Method

파일 이름에 안전한 문자열로 변환합니다.

name— 원본 문자열입니다.

반환: 정리된 문자열입니다.

TryCreateCandidate Method

구문/시맨틱 정보를 바탕으로 생성 대상 후보를 만듭니다.

context— 구문 분석 컨텍스트입니다.
attributeSymbols— 사용할 Attribute 심볼 집합입니다.

반환: 유효한 후보이면 해당 모델을 반환하고, 아니면 을 반환합니다.

DreamineCommandSourceGenerator

BuildCommandPropertyName Method

생성할 커맨드 프로퍼티 이름을 결정합니다.

methodName— 원본 메서드 이름입니다.
commandNameOverride— 명시적으로 지정한 커맨드 이름입니다.

반환: 생성할 커맨드 프로퍼티 이름입니다.

BuildFileName Method

생성 파일 이름을 만듭니다.

candidate— 대상 후보입니다.

반환: 생성 파일 이름입니다.

BuildSource Method

생성 코드를 만듭니다.

candidate— 생성 대상 후보입니다.

반환: 생성된 C# 소스 문자열입니다.

Emit Method

수집된 후보를 진단하고 소스를 생성합니다.

context— 소스 출력 컨텍스트입니다.
candidates— 수집된 후보 목록입니다.
GetConstructorStringArgument Method

생성자 문자열 인자를 가져옵니다.

attribute— 검사할 Attribute 데이터입니다.
index— 가져올 생성자 인덱스입니다.

반환: 값이 있으면 문자열을 반환하고, 아니면 을 반환합니다.

GetContainingTypeChain Method

바깥 타입부터 안쪽 타입까지의 체인을 반환합니다.

innerMostType— 가장 안쪽 타입입니다.

반환: 바깥쪽부터 정렬된 타입 체인입니다.

GetMethodSignatureWithoutAttributes Method

원본 메서드에서 Attribute와 본문을 제거한 시그니처를 만듭니다.

methodSyntax— 원본 메서드 구문입니다.

반환: 생성용 메서드 시그니처 문자열입니다.

GetNamedStringArgument Method

named argument 문자열 값을 가져옵니다.

attribute— 검사할 Attribute 데이터입니다.
name— 찾을 인자 이름입니다.

반환: 값이 있으면 문자열을 반환하고, 아니면 을 반환합니다.

GetTypeDeclarationHeader Method

partial 타입 선언 헤더를 생성합니다.

typeSymbol— 대상 타입 심볼입니다.

반환: 생성용 타입 선언 헤더입니다.

HasConflictingMember Method

같은 이름의 멤버가 이미 존재하는지 확인합니다.

typeSymbol— 검사할 타입입니다.
memberName— 검사할 멤버 이름입니다.

반환: 같은 이름의 멤버가 있으면 이고, 아니면 입니다.

Initialize Method

증분 생성기 파이프라인을 초기화합니다.

context— 생성기 초기화 컨텍스트입니다.
IsContainingTypePartial Method

containing type이 partial인지 확인합니다.

typeSymbol— 검사할 타입 심볼입니다.

반환: partial 타입이면 이고, 아니면 입니다.

IsParameterlessVoidMethod Method

메서드가 parameterless void 형식인지 확인합니다.

methodSymbol— 검사할 메서드 심볼입니다.

반환: 조건을 만족하면 이고, 아니면 입니다.

IsPartialMethod Method

partial 메서드 여부를 확인합니다.

methodSyntax— 검사할 메서드 구문입니다.

반환: partial 메서드이면 이고, 아니면 입니다.

NormalizeInvocation Method

TargetMethod 문자열을 호출 형태로 정규화합니다.

targetMethod— 원본 대상 메서드 문자열입니다.

반환: 호출 형태로 정규화된 문자열입니다.

Sanitize Method

파일명에 안전한 형태로 문자열을 정리합니다.

name— 원본 문자열입니다.

반환: 정리된 문자열입니다.

ToCamel Method

PascalCase 문자열을 camelCase로 변환합니다.

name— 변환할 문자열입니다.

반환: camelCase 문자열입니다.

TryCreateCandidate Method

구문/시맨틱 정보를 기반으로 생성 후보를 구성합니다.

context— 구문 분석 컨텍스트입니다.
attributeSymbol— DreamineCommandAttribute 심볼입니다.

반환: 유효한 후보이면 모델을 반환하고, 아니면 을 반환합니다.

ValidateCandidate Method

후보의 유효성을 검증합니다.

candidate— 검사할 후보입니다.

반환: 발견된 진단 목록입니다.

DreamineEntryGenerator

BuildEntrySource Method

엔트리 부트스트랩 소스 코드를 생성합니다.

namespaceName— 대상 네임스페이스입니다.
className— 대상 클래스 이름입니다.

반환: 생성된 C# 소스 코드 문자열입니다.

Emit Method

수집된 엔트리 후보를 진단하고 소스를 생성합니다.

context— 소스 출력 컨텍스트입니다.
candidates— 수집된 엔트리 후보 목록입니다.
InheritsFrom Method

대상 클래스가 지정한 기반 타입을 상속하는지 확인합니다.

symbol— 검사할 클래스 심볼입니다.
baseTypeSymbol— 기준 기반 타입 심볼입니다.

반환: 상속 관계가 있으면 이고, 아니면 입니다.

Initialize Method

증분 생성기 파이프라인을 초기화합니다.

context— 생성기 초기화 컨텍스트입니다.
IsCandidateSyntax Method

엔트리 후보가 될 수 있는 구문인지 확인합니다.

node— 검사할 구문 노드입니다.

반환: 후보가 될 수 있으면 이고, 아니면 입니다.

IsPartial Method

클래스 선언이 partial인지 확인합니다.

classDeclaration— 검사할 클래스 선언입니다.

반환: partial이면 이고, 아니면 입니다.

TryCreateCandidate Method

구문/시맨틱 정보를 기반으로 엔트리 후보 모델을 생성합니다.

context— 구문 분석 컨텍스트입니다.
entryAttributeSymbol— DreamineEntryAttribute 심볼입니다.
applicationSymbol— WPF Application 심볼입니다.

반환: 엔트리 후보이면 를 반환하고, 아니면 을 반환합니다.

EntryCandidateModel

#ctor Method

\if KO 클래스의 새 인스턴스를 초기화합니다. \endif \if EN Initializes a new instance of the class with the specified settings. \endif

classSymbol— \if KO 대상 클래스 심볼입니다. \endif \if EN The value used for class symbol. \endif
classDeclaration— \if KO 대상 클래스 선언 구문입니다. \endif \if EN The value used for class declaration. \endif
namespace— \if KO 대상 네임스페이스입니다. \endif \if EN The value used for namespace. \endif
className— \if KO 대상 클래스 이름입니다. \endif \if EN The value used for class name. \endif
isPartial— \if KO partial 선언 여부입니다. \endif \if EN The value used for is partial. \endif
isApplicationDerived— \if KO Application 상속 여부입니다. \endif \if EN The value used for is application derived. \endif
ClassDeclaration Property

\if KO 대상 클래스 선언 구문을 가져옵니다. \endif \if EN Gets the class declaration value. \endif

ClassName Property

\if KO 대상 클래스 이름을 가져옵니다. \endif \if EN Gets the class name value. \endif

ClassSymbol Property

\if KO 대상 클래스 심볼을 가져옵니다. \endif \if EN Gets the class symbol value. \endif

IsApplicationDerived Property

\if KO Application 상속 여부를 가져옵니다. \endif \if EN Gets the is application derived value. \endif

IsPartial Property

\if KO partial 선언 여부를 가져옵니다. \endif \if EN Gets the is partial value. \endif

Namespace Property

\if KO 대상 네임스페이스를 가져옵니다. \endif \if EN Gets the namespace value. \endif