@@ -206,6 +206,20 @@ file partial record struct TSelf(Guid Value) : IId
206
206
}
207
207
```
208
208
209
+ Another example of a built-in template that applies to a single type of ` TValue ` is
210
+ the following:
211
+
212
+ ``` csharp
213
+ using StructId ;
214
+
215
+ [TStructId ]
216
+ file partial record struct TSelf (string Value )
217
+ {
218
+ public static implicit operator string (TSelf id ) => id .Value ;
219
+ public static explicit operator TSelf (string value ) => new (value );
220
+ }
221
+ ```
222
+
209
223
This template is a proper C# compilation unit, so you can use any C# feature that
210
224
your project supports, since its output will also be emitted via a source generator
211
225
in the same project for matching struct ids.
@@ -228,10 +242,101 @@ partial record struct PersonId : IId
228
242
229
243
Things to note at template expansion time:
230
244
1 . The ` [TStructId] ` attribute is removed from the generated type automatically.
231
- 1 . The ` TSelf ` type is replaced with the actual name of the struct id.
232
- 1 . The primary constructor on the template is removed since it is already provided
233
- by anoother generator.
245
+ 2 . The ` TSelf ` type is replaced with the actual name of the struct id.
246
+ 3 . The primary constructor on the template is removed since it is already provided
247
+ by another generator.
248
+
249
+ You can also constrain the type of ` TValue ` the template applies to by using using
250
+ the special name ` TValue ` for the primary constructor parameter type, as in the following
251
+ example from the implicit conversion template:
252
+
253
+ ``` csharp
254
+ using StructId ;
255
+
256
+ [TStructId ]
257
+ file partial record struct TSelf (TValue Value )
258
+ {
259
+ public static implicit operator TValue (TSelf id ) => id .Value ;
260
+ public static explicit operator TSelf (TValue value ) => new (value );
261
+ }
262
+
263
+ file record struct TValue ;
264
+ ```
265
+
266
+ The ` TValue ` is subsequently defined as a file-local type where you can
267
+ specify whether it's a struct or a class and any interfaces it implements.
268
+ These are used to constrain the template expansion to only apply to struct ids,
269
+ such as those whose ` TValue ` is a struct above.
270
+
271
+ Here's another example from the built-in templates that uses this technique to
272
+ apply to all struct ids whose ` TValue ` implements ` IComparable<TValue> ` :
273
+
274
+ ``` csharp
275
+ using System ;
276
+ using StructId ;
277
+
278
+ [TStructId ]
279
+ file partial record struct TSelf (TValue Value ) : IComparable <TSelf >
280
+ {
281
+ /// <inheritdoc />
282
+ public int CompareTo (TSelf other ) => ((IComparable <TValue >)Value ).CompareTo (other .Value );
283
+
284
+ /// <inheritdoc />
285
+ public static bool operator < (TSelf left , TSelf right ) => left .Value .CompareTo (right .Value ) < 0 ;
286
+
287
+ /// <inheritdoc />
288
+ public static bool operator <= (TSelf left , TSelf right ) => left .Value .CompareTo (right .Value ) <= 0 ;
289
+
290
+ /// <inheritdoc />
291
+ public static bool operator > (TSelf left , TSelf right ) => left .Value .CompareTo (right .Value ) > 0 ;
292
+
293
+ /// <inheritdoc />
294
+ public static bool operator >= (TSelf left , TSelf right ) => left .Value .CompareTo (right .Value ) >= 0 ;
295
+ }
296
+
297
+ file record struct TValue : IComparable <TValue >
298
+ {
299
+ public int CompareTo (TValue other ) => throw new NotImplementedException ();
300
+ }
301
+ ```
302
+
303
+ This automatically covers not only all built-in value types, but also any custom
304
+ types that implement the interface, making the code generation much more flexible
305
+ and powerful.
306
+
307
+ In addition to constraining on the ` TValue ` type, you can also constrain on the
308
+ the struct id/` TSelf ` itself by declaring the inheritance requirements in a partial
309
+ class of ` TSelf ` in the template. For example, the following (built-in) template
310
+ ensures it's only applied/expanded for struct ids whose ` TValue ` is [ Ulid] ( https://github.com/Cysharp/Ulid )
311
+ and implement ` INewable<TSelf, Ulid> ` . Its usefulness in this case is that
312
+ the given interface constraint allows us to use the ` TSelf.New(Ulid) ` static interface
313
+ factory method and have it recognized by the C# compiler as valid code as part of the
314
+ implementation of the parameterless ` New() ` factory method:
315
+
316
+ ``` csharp
317
+ [TStructId ]
318
+ file partial record struct TSelf (Ulid Value )
319
+ {
320
+ public static TSelf New () => new (Ulid .NewUlid ());
321
+ }
322
+
323
+ // This will be removed when applying the template to each user-defined struct id.
324
+ file partial record struct TSelf : INewable <TSelf , Ulid >
325
+ {
326
+ public static TSelf New (Ulid value ) => throw new NotImplementedException ();
327
+ }
328
+ ```
234
329
330
+ > NOTE: the built-in templates will always provide an implementation of
331
+ > ` INewable<TSelf, TValue> ` .
332
+
333
+ Here you can see that the constraint that the value type must be ` Ulid ` is enforced by
334
+ the ` TValue ` constructor parameter type, while the interface constraint in the partial
335
+ declaration enforces inheritance from ` INewable<TSelf, Ulid> ` . Since this part of
336
+ the partial declaration is removed, there is no need to provide an actual implementation
337
+ for the constrain interface(s), just the signature is enough. But the partial declaration
338
+ providing the interface constraint is necessary for the C# compiler to recognize the
339
+ line with ` public static TSelf New() => new(Ulid.NewUlid()); ` as valid.
235
340
236
341
<!-- #content -->
237
342
<!-- #ci -->
0 commit comments