Dreamine.MVVM.Generators
Dreamine.MVVM.Generators는 Dreamine MVVM 생태계에서 사용하는 Roslyn 증분 소스 제너레이터 패키지입니다.
이 패키지는 Attribute로 선언한 의도를 기반으로 MVVM 보일러플레이트 코드를 컴파일 타임에 생성합니다.
현재 기준으로 다루는 주요 Attribute는 다음과 같습니다.
DreaminePropertyDreamineEntryDreamineModelDreamineEventDreamineCommand
이 패키지의 목표는 반복 코드를 줄이되, 생성 규칙과 제약을 명시적으로 유지하는 것입니다.
이 패키지가 하는 일
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.AttributesDreamine.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,ShowMainWindowpartial hook 생성
현재 제약
- 대상 타입은 partial 이어야 함
- 대상 타입은
System.Windows.Application을 상속해야 함 - 유효한 엔트리 타입은 하나만 허용하는 방향을 전제로 함
예시
using Dreamine.MVVM.Attributes;
[DreamineEntry]
public partial class App : Application
{
}
2) DreamineAutoWiringGenerator
[DreamineProperty], [DreamineModel], [DreamineEvent]가 적용된 필드를 기준으로 보조 프로퍼티를 생성합니다.
현재 역할
_title→Title_model→Model_event→Event
현재 기준
- 필드 기반 생성만 처리
- 속성(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 propertyModel→ model access propertyEvent→ event access property
주의 사항
[DreamineProperty]생성 코드는SetProperty(ref field, value)사용을 전제로 함
즉 대상 타입에SetProperty가 존재해야 함[DreamineModel],[DreamineEvent]는 현재 생성 정책상 readonly 필드 사용을 권장하지 않음DreamineModel은new T()초기화 경로를 사용함DreamineEvent는DMContainer.Resolve<T>()초기화 경로를 사용함- 생성 코드에서 더 이상
ViewModelBase상속을 강제하지 않음
3) DreamineCommandSourceGenerator
[DreamineCommand]가 적용된 메서드를 기준으로 ICommand 프로퍼티를 생성합니다.
현재 역할
{MethodName}Command프로퍼티 생성- 필요 시
CommandNameoverride 지원 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가 생성됩니다.
생성 예:
TitlepropertySaveCommandLoadReadmeCommand- forwarding method body
현재 코드 기준 중요 메모
1) 생성기는 완전 독립형 런타임 프레임워크가 아니다
생성 코드가 참조하는 런타임 개념은 여전히 존재합니다.
예:
DMContainerViewModelLocatorSetProperty
즉 이 패키지는 Dreamine MVVM 스택 안에서 사용하는 것을 전제로 합니다.
2) Attribute별 사용 범위가 동일하지 않다
현재 기준으로:
DreamineEntry→ App / bootstrap 계층DreamineProperty→SetProperty가능한 ViewModel 계층DreamineModel,DreamineEvent→ field access 생성DreamineCommand→ method 기반 command 생성
3) 생성 규칙은 점진적으로 엄격해지는 방향이다
현재 Generator 구현은 단순 자동 생성보다 다음을 더 중시합니다.
- partial 타입 검증
- 메서드 시그니처 검증
- 이름 충돌 방지
- 잘못된 사용에 대한 Diagnostic 추가
Packaging Notes
현재 프로젝트는 Analyzer 패키지 방향으로 구성하는 것을 전제로 합니다.
일반적인 구성 포인트:
PackageType=AnalyzerOutputItemType=AnalyzerIncludeBuildOutput=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 --> RelayCommandAttributeAPI 문서
타입
Attribute 심볼 집합을 나타냅니다.
자동 생성 대상 필드 메타데이터를 나타냅니다.
생성 대상 종류를 나타냅니다.
생성 대상 메서드 메타데이터를 나타냅니다.
DreamineModelAttribute, DreamineEventAttribute, DreaminePropertyAttribute가 적용된 필드를 기반으로 보조 프로퍼티 코드를 생성하는 증분 생성기입니다.
DreamineCommandAttribute가 적용된 메서드를 기반으로 커맨드 프로퍼티와 forwarding 메서드 구현을 생성하는 증분 생성기입니다.
DreamineEntryAttribute가 적용된 WPF 엔트리 클래스를 분석하고 Dreamine 부트스트랩 코드를 생성하는 증분 생성기입니다.
\if KO Entry Candidate Model 기능과 관련 상태를 캡슐화합니다. \endif \if EN Encapsulates entry candidate model functionality and related state. \endif
AttributeSymbolSet
클래스의 새 인스턴스를 초기화합니다.
modelAttribute— Model Attribute 심볼입니다.eventAttribute— Event Attribute 심볼입니다.propertyAttribute— Property Attribute 심볼입니다.Event Attribute 심볼을 가져옵니다.
필수 심볼이 모두 준비되었는지 여부를 가져옵니다.
Model Attribute 심볼을 가져옵니다.
Property Attribute 심볼을 가져옵니다.
AutoWiringCandidate
클래스의 새 인스턴스를 초기화합니다.
fieldSymbol— 대상 필드 심볼입니다.containingType— 필드를 포함하는 타입입니다.kind— 후보 종류입니다.generatedPropertyName— 생성할 프로퍼티 이름입니다.필드를 포함하는 타입을 가져옵니다.
대상 필드 심볼을 가져옵니다.
생성할 프로퍼티 이름을 가져옵니다.
후보 종류를 가져옵니다.
CommandCandidateModel
클래스의 새 인스턴스를 초기화합니다.
methodSyntax— 원본 메서드 구문입니다.methodSymbol— 원본 메서드 심볼입니다.targetMethod— 대상 메서드 경로입니다.bindTo— 반환값을 대입할 프로퍼티 이름입니다.commandPropertyName— 생성할 커맨드 프로퍼티 이름입니다.canExecuteMethod— 커맨드 실행 가능 여부를 확인할 메서드 이름입니다.반환값을 대입할 프로퍼티 이름을 가져옵니다.
CanExecute 판단 메서드 이름을 가져옵니다.
생성할 커맨드 프로퍼티 이름을 가져옵니다.
원본 메서드 심볼을 가져옵니다.
원본 메서드 구문을 가져옵니다.
대상 메서드 경로를 가져옵니다.
DreamineAutoWiringGenerator
지연 초기화 기반 읽기 전용 프로퍼티 코드를 생성합니다.
builder— 대상 문자열 빌더입니다.fieldSymbol— 원본 필드 심볼입니다.typeName— 필드 타입 이름입니다.fieldName— 필드 이름입니다.propertyName— 프로퍼티 이름입니다.initializerExpression— 초기화 식입니다.종류에 맞는 프로퍼티 코드를 생성합니다.
builder— 대상 문자열 빌더입니다.kind— 생성 대상 종류입니다.fieldSymbol— 원본 필드 심볼입니다.typeName— 필드 타입 이름입니다.fieldName— 필드 이름입니다.propertyName— 생성할 프로퍼티 이름입니다.생성 파일 이름을 만듭니다.
candidate— 대상 후보입니다.반환: 생성 파일 이름입니다.
생성 코드를 만듭니다.
candidate— 생성 대상 후보입니다.반환: 생성된 C# 소스 문자열입니다.
수집된 후보를 기반으로 소스를 생성합니다.
context— 소스 출력 컨텍스트입니다.candidates— 수집된 후보 목록입니다.필드에 적용된 Attribute 종류를 판별합니다.
fieldSymbol— 검사할 필드 심볼입니다.attributeSymbols— 비교할 Attribute 심볼 집합입니다.matchedAttribute— 일치한 Attribute 데이터입니다.반환: 일치한 종류가 있으면 해당 를 반환하고, 아니면 을 반환합니다.
Attribute에 지정된 명시적 프로퍼티 이름을 가져옵니다.
attribute— 검사할 Attribute 데이터입니다.반환: 설정된 이름이 있으면 반환하고, 없으면 을 반환합니다.
이미 같은 이름의 멤버가 존재하는지 확인합니다.
typeSymbol— 검사 대상 타입입니다.memberName— 확인할 멤버 이름입니다.반환: 같은 이름의 멤버가 있으면 이고, 아니면 입니다.
증분 생성기 파이프라인을 초기화합니다.
context— 생성기 초기화 컨텍스트입니다.후보가 될 수 있는 구문인지 확인합니다.
node— 검사할 구문 노드입니다.반환: 후보 구문이면 이고, 아니면 입니다.
생성할 프로퍼티 이름을 결정합니다.
fieldName— 원본 필드 이름입니다.configuredPropertyName— 명시적으로 지정된 프로퍼티 이름입니다.반환: 유효한 프로퍼티 이름이면 반환하고, 아니면 을 반환합니다.
파일 이름에 안전한 문자열로 변환합니다.
name— 원본 문자열입니다.반환: 정리된 문자열입니다.
구문/시맨틱 정보를 바탕으로 생성 대상 후보를 만듭니다.
context— 구문 분석 컨텍스트입니다.attributeSymbols— 사용할 Attribute 심볼 집합입니다.반환: 유효한 후보이면 해당 모델을 반환하고, 아니면 을 반환합니다.
DreamineCommandSourceGenerator
생성할 커맨드 프로퍼티 이름을 결정합니다.
methodName— 원본 메서드 이름입니다.commandNameOverride— 명시적으로 지정한 커맨드 이름입니다.반환: 생성할 커맨드 프로퍼티 이름입니다.
생성 파일 이름을 만듭니다.
candidate— 대상 후보입니다.반환: 생성 파일 이름입니다.
생성 코드를 만듭니다.
candidate— 생성 대상 후보입니다.반환: 생성된 C# 소스 문자열입니다.
수집된 후보를 진단하고 소스를 생성합니다.
context— 소스 출력 컨텍스트입니다.candidates— 수집된 후보 목록입니다.생성자 문자열 인자를 가져옵니다.
attribute— 검사할 Attribute 데이터입니다.index— 가져올 생성자 인덱스입니다.반환: 값이 있으면 문자열을 반환하고, 아니면 을 반환합니다.
바깥 타입부터 안쪽 타입까지의 체인을 반환합니다.
innerMostType— 가장 안쪽 타입입니다.반환: 바깥쪽부터 정렬된 타입 체인입니다.
원본 메서드에서 Attribute와 본문을 제거한 시그니처를 만듭니다.
methodSyntax— 원본 메서드 구문입니다.반환: 생성용 메서드 시그니처 문자열입니다.
named argument 문자열 값을 가져옵니다.
attribute— 검사할 Attribute 데이터입니다.name— 찾을 인자 이름입니다.반환: 값이 있으면 문자열을 반환하고, 아니면 을 반환합니다.
partial 타입 선언 헤더를 생성합니다.
typeSymbol— 대상 타입 심볼입니다.반환: 생성용 타입 선언 헤더입니다.
같은 이름의 멤버가 이미 존재하는지 확인합니다.
typeSymbol— 검사할 타입입니다.memberName— 검사할 멤버 이름입니다.반환: 같은 이름의 멤버가 있으면 이고, 아니면 입니다.
증분 생성기 파이프라인을 초기화합니다.
context— 생성기 초기화 컨텍스트입니다.containing type이 partial인지 확인합니다.
typeSymbol— 검사할 타입 심볼입니다.반환: partial 타입이면 이고, 아니면 입니다.
메서드가 parameterless void 형식인지 확인합니다.
methodSymbol— 검사할 메서드 심볼입니다.반환: 조건을 만족하면 이고, 아니면 입니다.
partial 메서드 여부를 확인합니다.
methodSyntax— 검사할 메서드 구문입니다.반환: partial 메서드이면 이고, 아니면 입니다.
TargetMethod 문자열을 호출 형태로 정규화합니다.
targetMethod— 원본 대상 메서드 문자열입니다.반환: 호출 형태로 정규화된 문자열입니다.
파일명에 안전한 형태로 문자열을 정리합니다.
name— 원본 문자열입니다.반환: 정리된 문자열입니다.
PascalCase 문자열을 camelCase로 변환합니다.
name— 변환할 문자열입니다.반환: camelCase 문자열입니다.
구문/시맨틱 정보를 기반으로 생성 후보를 구성합니다.
context— 구문 분석 컨텍스트입니다.attributeSymbol— DreamineCommandAttribute 심볼입니다.반환: 유효한 후보이면 모델을 반환하고, 아니면 을 반환합니다.
후보의 유효성을 검증합니다.
candidate— 검사할 후보입니다.반환: 발견된 진단 목록입니다.
DreamineEntryGenerator
엔트리 부트스트랩 소스 코드를 생성합니다.
namespaceName— 대상 네임스페이스입니다.className— 대상 클래스 이름입니다.반환: 생성된 C# 소스 코드 문자열입니다.
수집된 엔트리 후보를 진단하고 소스를 생성합니다.
context— 소스 출력 컨텍스트입니다.candidates— 수집된 엔트리 후보 목록입니다.대상 클래스가 지정한 기반 타입을 상속하는지 확인합니다.
symbol— 검사할 클래스 심볼입니다.baseTypeSymbol— 기준 기반 타입 심볼입니다.반환: 상속 관계가 있으면 이고, 아니면 입니다.
증분 생성기 파이프라인을 초기화합니다.
context— 생성기 초기화 컨텍스트입니다.엔트리 후보가 될 수 있는 구문인지 확인합니다.
node— 검사할 구문 노드입니다.반환: 후보가 될 수 있으면 이고, 아니면 입니다.
클래스 선언이 partial인지 확인합니다.
classDeclaration— 검사할 클래스 선언입니다.반환: partial이면 이고, 아니면 입니다.
구문/시맨틱 정보를 기반으로 엔트리 후보 모델을 생성합니다.
context— 구문 분석 컨텍스트입니다.entryAttributeSymbol— DreamineEntryAttribute 심볼입니다.applicationSymbol— WPF Application 심볼입니다.반환: 엔트리 후보이면 를 반환하고, 아니면 을 반환합니다.
EntryCandidateModel
\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. \endifclassDeclaration— \if KO 대상 클래스 선언 구문입니다. \endif \if EN The value used for class declaration. \endifnamespace— \if KO 대상 네임스페이스입니다. \endif \if EN The value used for namespace. \endifclassName— \if KO 대상 클래스 이름입니다. \endif \if EN The value used for class name. \endifisPartial— \if KO partial 선언 여부입니다. \endif \if EN The value used for is partial. \endifisApplicationDerived— \if KO Application 상속 여부입니다. \endif \if EN The value used for is application derived. \endif\if KO 대상 클래스 선언 구문을 가져옵니다. \endif \if EN Gets the class declaration value. \endif
\if KO 대상 클래스 이름을 가져옵니다. \endif \if EN Gets the class name value. \endif
\if KO 대상 클래스 심볼을 가져옵니다. \endif \if EN Gets the class symbol value. \endif
\if KO Application 상속 여부를 가져옵니다. \endif \if EN Gets the is application derived value. \endif
\if KO partial 선언 여부를 가져옵니다. \endif \if EN Gets the is partial value. \endif
\if KO 대상 네임스페이스를 가져옵니다. \endif \if EN Gets the namespace value. \endif