이 프로젝트를 좋아하거나 사용하는 경우 별을주십시오. 감사해요!
Vogen ( "Voh Jen"으로 발음 )은 .NET 소스 생성기 및 분석기입니다. 프리미티브 (ints, decimals 등)를 도메인 개념 (CustomerID, AccountBalance 등)을 나타내는 가치 객체로 바꿉니다.
새로운 C# 컴파일 오류가 추가되어 유효하지 않은 값 객체의 생성을 중지하는 데 도움이됩니다.
이 readme는 일부 기능을 다룹니다. 시작, 튜토리얼 및 방법과 같은 자세한 정보는 Wiki를 참조하십시오.
소스 생성기는 강력하게 입력 된 도메인 개념을 생성합니다. 당신은 이것을 제공합니다 :
[ ValueObject < int > ]
public partial struct CustomerId ;... 그리고 Vogen은 다음과 유사한 소스를 생성합니다.
public partial struct CustomerId :
System . IEquatable < CustomerId > ,
System . IComparable < CustomerId > , System . IComparable
{
private readonly int _value ;
public readonly int Value => _value ;
public CustomerId ( ) {
throw new Vogen . ValueObjectValidationException (
"Validation skipped by attempting to use the default constructor..." ) ;
}
private CustomerId ( int value ) => _value = value ;
public static CustomerId From ( int value ) {
CustomerId instance = new CustomerId ( value ) ;
return instance ;
}
public readonly bool Equals ( CustomerId other ) .. .
public readonly bool Equals ( int primitive ) .. .
public readonly override bool Equals ( object obj ) .. .
public static bool operator == ( CustomerId left , CustomerId right ) .. .
public static bool operator != ( CustomerId left , CustomerId right ) .. .
public static bool operator == ( CustomerId left , int right ) .. .
public static bool operator != ( CustomerId left , int right ) .. .
public static bool operator == ( int left , CustomerId right ) .. .
public static bool operator != ( int left , CustomerId right ) .. .
public readonly override int GetHashCode ( ) .. .
public readonly override string ToString ( ) .. .
} 그런 다음 도메인에서 int 대신 CustomerId 사용하여 유효하고 사용하는 것이 안전하다는 사실을 완전히 알고 있습니다.
CustomerId customerId = CustomerId . From ( 123 ) ;
SendInvoice ( customerId ) ;
.. .
public void SendInvoice ( CustomerId customerId ) { .. . } int 는 값 객체의 기본 유형입니다. 일반적으로 명확성을 위해 각 유형을 명시 적으로 선언하는 것이 좋습니다. 또한 다른 유형으로 구성 할 수 있습니다. 문서 후반부 구성 섹션을 참조하십시오.
다른 예는 다음과 같습니다.
[ ValueObject < decimal > ]
public partial struct AccountBalance ;
[ ValueObject < string > ]
public partial class LegalEntityName ;Vogen의 주요 목표는 값 객체의 유효성을 보장하는 것 입니다. 코드 분석기는 도메인의 초기화되지 않은 값 객체를 남길 수있는 실수를 피하는 데 도움이됩니다.
새로운 C# 컴파일 오류의 형태로 새로운 제약 조건을 추가하여 이를 수행합니다. 초기화되지 않은 값 객체로 끝날 수있는 몇 가지 방법이 있습니다. 한 가지 방법은 유형 생성자에게 제공하는 것입니다. 자신의 생성자를 제공한다는 것은 값을 설정하는 것을 잊어 버릴 수 있으므로 Vogen은 사용자 정의 생성자를 가질 수 없습니다 .
[ ValueObject ]
public partial struct CustomerId
{
// Vogen deliberately generates this so that you can't create your own:
// error CS0111: Type 'CustomerId' already defines a member called 'CustomerId'
// with the same parameter type
public CustomerId ( ) { }
// error VOG008: Cannot have user defined constructors,
// please use the From method for creation.
public CustomerId ( int value ) { }
}또한 Vogen은 가치 객체를 만들 거나 소비 할 때 문제를 발견합니다.
// catches object creation expressions
var c = new CustomerId ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
CustomerId c = default ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
var c = default ( CustomerId ) ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
var c = GetCustomerId ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
var c = Activator . CreateInstance < CustomerId > ( ) ; // error VOG025: Type 'CustomerId' cannot be constructed via Reflection as it is prohibited.
var c = Activator . CreateInstance ( < CustomerId > ) ; // error VOG025: Type 'MyVo' cannot be constructed via Reflection as
it is prohibited
// catches lambda expressions
Func < CustomerId > f = ( ) => default ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
// catches method / local function return expressions
CustomerId GetCustomerId ( ) => default ; // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
CustomerId GetCustomerId ( ) => new CustomerId ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
CustomerId GetCustomerId ( ) => new ( ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
// catches argument / parameter expressions
Task < CustomerId > t = Task . FromResult < CustomerId > ( new ( ) ) ; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' as it is prohibited
void Process ( CustomerId customerId = default ) { } // error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited. 이 프로젝트의 주요 목표 중 하나는 프리미티브를 직접 사용하는 것과 거의 동일한 속도와 메모리 성능을 달성하는 것입니다. 다른 방법 으로 , decimal 원시가 계정 잔액을 나타내는 경우 대신 AccountBalance 아래의 성능 메트릭을 참조하십시오.
Vogen은 Nuget 패키지입니다. 다음과 같이 설치하십시오.
dotnet add package Vogen
프로젝트에 추가되면 소스 생성기는 프리미티브 용 래퍼를 생성하고 코드 분석기는 잘못된 값 객체를 만들려고 할 때 알려줍니다.
도메인 개념 과 도메인 개념과 기본 요소를 사용하여이를 나타내는 방법에 대해 생각해보십시오.
public void HandlePayment ( int customerId , int accountId , decimal paymentAmount )... 이것을 가지고 :
public void HandlePayment ( CustomerId customerId , AccountId accountId , PaymentAmount paymentAmount )다음과 같은 유형을 만드는 것만 큼 간단합니다.
[ ValueObject ]
public partial struct CustomerId ;
[ ValueObject ]
public partial struct AccountId ;
[ ValueObject < decimal > ]
public partial struct PaymentAmount ; 소스 생성기는 값 객체를 생성합니다. 값 객체는 int , string , double 등과 같은 간단한 프리미티브를 강하게 유형으로 감싸서 원시 강박 관념에 맞서도록 도와줍니다.
원시적 강박 관념 (일명 Stryingtyped)은 프리미티브에 집착하는 것을 의미합니다. 소프트웨어의 품질을 저하시키는 코드 냄새입니다.
" 원시 강박 관념은 원시 데이터 유형을 사용하여 도메인 아이디어를 나타냅니다 " #
몇 가지 예 :
int age 대신 - 우리는 Age age 있습니다. Age 부정적 일 수 없다는 검증이있을 수 있습니다string postcode 대신 - Postcode postcode 가 있습니다. Postcode 텍스트 형식으로 유효성이있을 수 있습니다.소스 생성기는 의견이 있습니다. 의견은 일관성을 보장하는 데 도움이됩니다. 의견은 다음과 같습니다.
From 이름이 지정된 공장 방법을 통해 구성됩니다 Age.From(12)Age.From(12) == Age.From(12) )Validation 결과를 반환하는 Validate 라는 정적 메소드로 검증됩니다.Validation.Ok ValueObjectValidationException .OK 도메인 아이디어를 프리미티브로 표현하는 것이 일반적이지만 프리미티브는 도메인 아이디어를 완전히 설명하지 못할 수 있습니다.
프리미티브 대신 가치 객체를 사용하기 위해서는 다음과 같은 코드를 교체합니다.
public class CustomerInfo {
private int _id ;
public CustomerInfo ( int id ) => _id = id ;
}.. 이것에 :
public class CustomerInfo {
private CustomerId _id ;
public CustomerInfo ( CustomerId id ) => _id = id ;
} 여기에 설명하는 블로그 게시물이 있지만 요약합니다.
원시적 집착은
ints및strings과 같은 프리미티브가 도메인 객체와 아이디어를 나타낼 수있게하는 겉보기 에 편리한 방법에 집착 하고 있습니다.
이건 :
int customerId = 42그게 뭐가 잘못 됐나요?
고객 ID는 int 로 완전히 대표 할 수 없습니다. int 음수이거나 0 일 수 있지만 고객 ID가 가능하지는 않습니다. 따라서 고객 ID에 제약이 있습니다. 우리는 int 에서 이러한 제약을 대표 하거나 시행 할 수 없습니다.
따라서 고객 ID의 제약을 충족시키기 위해 약간의 검증이 필요합니다. int 에 있기 때문에 미리 확인되었는지 확인할 수 없으므로 사용할 때마다 확인해야합니다. 원시적이기 때문에 누군가가 값을 변경했을 수도 있으므로, 우리가 전에 확인한 100% 확신하더라도 여전히 다시 확인해야 할 수도 있습니다.
지금까지, 우리는 예를 들어, 고객 ID 42 를 사용했습니다. C#에서는 " 42 == 42 "( JavaScript에서 확인하지 않았다는 것은 놀라운 일이 아닙니다! ). 그러나 우리 도메인 에서 42 항상 42 와 같아야합니까? 42 의 공급 업체 ID를 42 의 고객 ID와 비교하는 경우가 아닐 수도 있습니다! 그러나 프리미티브는 여기에서 당신을 도울 수 없습니다 ( 42 == 42 !).
( 42 == 42 ) // true
( SuppliedId . From ( 42 ) == SupplierId . From ( 42 ) ) // true
( SuppliedId . From ( 42 ) == VendorId . From ( 42 ) ) // compilation error 그러나 때로는 값 객체가 유효하지 않거나 설정되지 않았 음을 나타냅니다. 우리는 우연히 사용할 수 있으므로 물체 이외의 사람 이이 작업을 수행하는 것을 원하지 않습니다. Unspecified 인스턴스를 갖는 것이 일반적입니다
public class Person {
public Age Age { get ; } = Age . Unspecified ;
} Instance 속성으로 다음을 수행 할 수 있습니다.
[ ValueObject ]
[ Instance ( "Unspecified" , - 1 ) ]
public readonly partial struct Age {
public static Validation Validate ( int value ) =>
value > 0 ? Validation . Ok : Validation . Invalid ( "Must be greater than zero." ) ;
} 이것은 public static Age Unspecified = new Age(-1); . 생성자는 private 이므로이 유형만이 유효하지 않은 인스턴스를 만들 수 있습니다.
이제 Age 사용하면 검증이 더 명확 해집니다.
public void Process ( Person person ) {
if ( person . Age == Age . Unspecified ) {
// age not specified.
}
}다른 인스턴스 속성을 지정할 수도 있습니다.
[ ValueObject < float > ]
[ Instance ( "Freezing" , 0 ) ]
[ Instance ( "Boiling" , 100 ) ]
public readonly partial struct Celsius {
public static Validation Validate ( float value ) =>
value >= - 273 ? Validation . Ok : Validation . Invalid ( "Cannot be colder than absolute zero" ) ;
} 각 값 객체는 자체 선택적 구성을 가질 수 있습니다. 구성은 다음과 같습니다.
위의 것 중 하나가 지정되지 않으면 글로벌 구성이 추론됩니다. 다음과 같이 보입니다.
[ assembly : VogenDefaults (
underlyingType : typeof ( int ) ,
conversions : Conversions . Default ,
throws : typeof ( ValueObjectValidationException ) ) ]그것들은 다시 선택 사항입니다. 그들이 지정되지 않은 경우, 기본값은 다음과 같습니다.
typeof(int)Conversions.Default ( TypeConverter 및 System.Text.Json )typeof(ValueObjectValidationException)유효하지 않은 구성에 대한 몇 가지 코드 분석 경고가 있습니다.
System.Exception 에서 파생되지 않는 예외를 지정할 때 (이것들을 직접 실행하려면 : dotnet run -c Release --framework net9.0 -- --job short --filter * Vogen.Benchmarks 폴더의 job short -filter *)
앞에서 언급했듯이 Vogen의 목표는 프리미티브 자체를 사용하는 것과 비교하여 매우 유사한 성능을 달성하는 것입니다. 다음은 기본적으로 ( 원시적으로 ?)를 사용하여 검증 된 값 객체의 사용을 기본 유형의 int vs와 비교하는 벤치 마크입니다.
BenchmarkDotNet =v0.13.2, OS =Windows 11 (10.0.22621.1194)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK =7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
ShortRun : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job =ShortRun IterationCount =3 LaunchCount =1
WarmupCount =3 | 방법 | 평균 | 오류 | stddev | 비율 | 비율 | gen0 | 할당 |
|---|---|---|---|---|---|---|---|
| 정보를 사용합니다 | 14.55 ns | 1.443 ns | 0.079 ns | 1.00 | 0.00 | - | - |
| 사용 valueobjectStruct | 14.88 ns | 3.639 ns | 0.199 ns | 1.02 | 0.02 | - | - |
네이티브 INT와 VO 구조물 사용 사이에는 분명한 차이가 없습니다. 둘 다 속도와 메모리 측면에서 거의 동일합니다.
다음으로 가장 일반적인 시나리오는 VO 클래스를 사용하여 기본 String 나타냅니다. 이 결과는 다음과 같습니다.
BenchmarkDotNet =v0.13.2, OS =Windows 11 (10.0.22621.1194)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK =7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
ShortRun : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
Job =ShortRun IterationCount =3 LaunchCount =1
WarmupCount =3 | 방법 | 평균 | 오류 | stddev | 비율 | 비율 | gen0 | 할당 | 할당 비율 |
|---|---|---|---|---|---|---|---|---|
| 유사 적으로 | 151.8 ns | 32.19 | 1.76 | 1.00 | 0.00 | 0.0153 | 256 b | 1.00 |
| 사용 valueobjectasstruct | 184.8 ns | 12.19 | 0.67 | 1.22 | 0.02 | 0.0153 | 256 b | 1.00 |
소량의 성능 오버 헤드가 있지만 이러한 측정은 엄청나게 작습니다. 메모리 오버 헤드가 없습니다.
기본적으로 각 VO는 TypeConverter 및 System.Text.Json (STJ) 시리얼 라이저로 장식됩니다. 다른 변환기/시리얼 라이저가 있습니다.
그것들은 Conversions 열거에 의해 제어됩니다. 다음에는 NSJ 및 STJ 용 직렬화가 있습니다.
[ ValueObject < float > ( conversions : Conversions . NewtonsoftJson | Conversions . SystemTextJson ) ]
public readonly partial struct Celsius ; 전환을 원하지 않으면 Conversions.None 지정하십시오.
자신의 변환을 원한다면 다른 유형과 마찬가지로 다시 아무것도 지정하지 않고 직접 구현하십시오. 그러나 Serializers조차도 VOS를 만들려고 할 때 new default 에 대해 동일한 컴파일 오류를 얻을 수 있습니다.
Dapper를 사용하려면 등록하는 것을 잊지 마십시오.
SqlMapper . AddTypeHandler ( new Customer . DapperTypeHandler ( ) ) ;자세한 내용은 예제 폴더를 참조하십시오.
다음은 위키의 전체 FAQ 페이지에서 발췌 한 것입니다.
예, 여기 있습니다 : https://stevedunn.github.io/vogen/vogen.html
소스 생성기는 .NET 표준 2.0입니다. 생성 된 코드는 6.0 이상의 모든 C# 언어 버전을 지원합니다.
.NET 프레임 워크 프로젝트에서 발전기를 사용하고 구식 프로젝트 ( 'SDK 스타일'프로젝트 이전의 프로젝트)를 사용하는 경우 몇 가지 작업을 다르게 수행해야합니다.
PackageReference 사용하여 참조를 추가하십시오. < ItemGroup >
< PackageReference Include = " Vogen " Version = " [LATEST_VERSION_HERE - E.G. 1.0.18] " PrivateAssets = " all " />
</ ItemGroup >latest (또는 8 개 이상)으로 설정하십시오. <PropertyGroup>
+ <LangVersion>latest</LangVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
값 객체라는 용어는 평등이 정체성이 아닌 가치를 기반으로하는 작은 대상을 나타냅니다. Wikipedia에서
컴퓨터 과학에서 값 객체는 평등이 정체성을 기반으로하지 않는 단순한 개체를 나타내는 작은 객체입니다. 즉, 두 값 객체는 동일한 값을 가질 때 동일하지만 반드시 같은 객체가 아니라는 것은 동일합니다.
DDD에서 값 객체는 (다시 Wikipedia에서)입니다.
... 가치 객체는 속성을 포함하지만 개념적 정체성이없는 불변의 객체입니다.
public record struct CustomerId(int Value); ? 그것은 당신에게 검증을 제공하지 않습니다. Value 확인하기 위해 속기 구문 (1 차 생성자)을 사용할 수 없습니다. 그래서 당신은해야합니다 :
public record struct CustomerId
{
public CustomerId ( int value ) {
if ( value <= 0 ) throw new Exception ( .. . )
}
} 또한 데이터를 검증하지 못하는 다른 생성자를 제공하여 도메인에 유효하지 않은 데이터를 허용 할 수 있습니다 . 다른 생성자는 예외를 던지지 않거나 다른 예외를 던질 수 있습니다. Vogen의 의견 중 하나는 값 객체에 주어진 잘못된 데이터가 ValueObjectValidationException 던진다는 것입니다.
default(CustomerId) 사용하여 유효성 검사를 피할 수도 있습니다. Vogen에는 이것을 잡고 빌드에 실패하는 분석기가 있습니다.
// error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
CustomerId c = default ;
// error VOG009: Type 'CustomerId' cannot be constructed with default as it is prohibited.
var c2 = default ( CustomerId ) ; 예. 기본적으로 각 VO는 TypeConverter 및 System.Text.Json (STJ) 시리얼 라이저로 장식됩니다. 다른 컨버터/시리얼 라이저가 있습니다.
예, 특정 고려 사항이 있지만. 위키의 efcore 페이지를 참조하십시오. 그러나 tl; dr은 다음과 같습니다.
[ValueObject<string>(conversions: Conversions.EfCoreValueConverter)] (예 : [valueObject <string>)를 생성해야하며 OnModelCreating 메소드에서 해당 변환기를 사용하도록 EFCORE에 알려야합니다. builder . Entity < SomeEntity > ( b =>
{
b . Property ( e => e . Name ) . HasConversion ( new Name . EfCoreValueConverter ( ) ) ;
} ) ;그러나 도메인 전체의 일관성을 보장하려면 어디에서나 검증 해야합니다. 그리고 얕은 법칙은 불가능하다고 말합니다.
⚖️ 얕은 법칙 "N이 변화해야하고 n> 1이 될 때, 얕은 것은 최대 n -1을 찾을 것입니다."
구체적으로 : "5 가지가 바뀌어야 할 때 얕은 곳은 최대 4 가지를 찾을 것입니다."
struct 인 경우 CustomerId customerId = default(CustomerId); ?예 . 분석기는 컴파일 오류를 생성합니다.
struct 인 경우 CustomerId customerId = new(CustomerId); ?예 . 분석기는 컴파일 오류를 생성합니다.
아니요 . 매개 변수가없는 생성자는 자동으로 생성되며 기본 값을 취하는 생성자도 자동으로 생성됩니다.
추가 생성자를 추가하면 코드 생성기에서 컴파일 오류가 발생합니다.
[ ValueObject < int > ) ]
public partial struct CustomerId {
// Vogen already generates this as a private constructor:
// error CS0111: Type 'CustomerId' already defines a member called 'CustomerId' with the same parameter type
public CustomerId ( ) { }
// error VOG008: Cannot have user defined constructors, please use the From method for creation.
public CustomerId ( int value ) { }
}할 수는 있지만 컴파일러 경고 CS0282- 부분 클래스 또는 구조물 '유형'의 여러 선언에서 필드간에 정의 된 순서는 없습니다.
예를 들어 입력을 다듬는 등 사용 된 값을 정상화/소독하고 싶습니다. 이것이 가능합니까?
예, NormalizeInput 메소드를 추가하십시오
private static string NormalizeInput ( string input ) => input . Trim ( ) ;자세한 내용은 Wiki를 참조하십시오.
그렇다면 좋을 것이지만 현재는 아닙니다. 나는 그것에 관한 기사를 썼지 만 요약하면, 정의 불가능한 가치 유형에 중점을 둔 오랜 언어 제안이 있습니다. 정복 불가능한 값 유형을 갖는 것은 첫 번째 단계이지만, 검증을 시행하기 위해 언어로 무언가를 갖는 것이 편리합니다. 그래서 나는 불변 기록에 대한 언어 제안을 추가했습니다.
이 제안의 응답 중 하나는 언어 팀이 검증 정책이 C#의 일부가 아니라 소스 생성기가 제공해야한다고 결정했다고 말합니다.
강한 pledid 이것은 ID에 더 중점을 둡니다. Vogen은 더 많은 '도메인 개념'과 해당 개념과 관련된 제약에 중점을 둡니다.
strytytyped 이것은 나의 첫 번째 시도이며 소스 생성이 아닙니다. 기본 유형이 클래스이기 때문에 메모리 오버 헤드가 있습니다. 분석기도 없습니다. 이제 Vogen에게 유리한 감가 상각 된 것으로 표시됩니다.
Strylytyped와 유사한 가치 - 비 소스 생성 및 분석기가 없습니다. 이것은 또한 더 편안하고 복합식 '기본'유형을 허용합니다.
ValueObjectGenerator Vogen과 유사하지만 검증 및 코드 분석기에 중점을 두지 않았습니다.
모든 유형을 포장 할 수 있습니다. 직렬화 및 유형 변환에는 다음에 대한 구현이 있습니다.
끈
int
긴
짧은
바이트
플로트 (싱글)
소수
더블
DateTime
날짜
Timeonly
dateTimeOffset
안내
부
다른 유형의 경우 일반 유형 변환 및 직렬 라이저가 적용됩니다. 유형 변환 및 직렬화를 위해 고유 한 변환기를 공급하는 경우 변환기에 None 지정하고 자신의 유형에 대한 속성으로 유형을 장식하십시오.
[ ValueObject < SpecialPrimitive > ( conversions : Conversions . None ) ]
[ System . Text . Json . Serialization . JsonConverter ( typeof ( SpecialPrimitiveJsonConverter ) ) ]
[ System . ComponentModel . TypeConverter ( typeof ( SpecialPrimitiveTypeConverter ) ) ]
public partial struct SpecialMeasurement ; 예, ValueObject 속성 또는 VogenConfiguration 에서 ValueObject 속성에서 예외 유형을 지정함으로써.
DateTime TimeOnly 로 변환 할 수 없다고 말하는 TimeOnly 를 사용할 때 오류가 발생합니다. 어떻게해야합니까? LINQ2DB 4.0 이상이 DateOnly 및 TimeOnly 지원합니다. Vogen은 LINQ2DB에 대한 값 변환기를 생성합니다. DateOnly 작동하지만`TimeOnly의 경우 응용 프로그램에 추가해야합니다.
MappingSchema.Default.SetConverter<DateTime, TimeOnly>(dt => TimeOnly.FromDateTime(dt));
예. protobuf-net에 종속성을 추가하고 대리 속성을 설정하십시오.
[ ValueObject < string > ]
[ ProtoContract ( Surrogate = typeof ( string ) ) ]
public partial class BoxId {
//...
}Boxid 유형은 이제 모든 메시지/grpc 호출에서 문자열로 직렬화됩니다. C# 코드의 다른 응용 프로그램에 대해 .proto 파일을 생성하는 경우 프로토 파일에는 대리 유형이 유형으로 포함됩니다. 이 정보에 대해 @domasm에 감사합니다 .
기여한 모든 사람들에게 감사합니다!
나는 Andrew Lock의 강력한 페디 디드에서 많은 영감을 얻었습니다.
나는 또한 Gérald Barré의 Meziantou.analyzer로부터 훌륭한 아이디어를 얻었습니다.