@@ -206,6 +206,20 @@ file partial record struct TSelf(Guid Value) : IId
206206}
207207```
208208
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+
209223This template is a proper C# compilation unit, so you can use any C# feature that
210224your project supports, since its output will also be emitted via a source generator
211225in the same project for matching struct ids.
@@ -228,10 +242,101 @@ partial record struct PersonId : IId
228242
229243Things to note at template expansion time:
2302441 . 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+ ```
234329
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.
235340
236341<!-- #content -->
237342<!-- #ci -->
0 commit comments