No. Vogen is primarily designed for the domain layer where nulls just add confusion. For instance, what does a null SettlmentAmount mean? But sometimes, in other layers, where nulls do exist, it can be useful to have convenience methods, for instance, FromNullable(decimal?) or FromNullable(int?).
If you need FromNullable, then, in your assembly (normally in your 'Infrastructure' layer if you're using Onion/Clean Architecture), you can specify that Vogen create the IVogen<TWrapper, TPrimitive> on all types. Then, you can add extension members (C# 14+ only).
The goal of this is to identify types that are generated by Vogen.
Use Case
I use Vogen alongside EfCore, I like to programmatically add ValueConverters by convention, I need to identity which properties on my entities are generated value objects.
Solution
Vogen decorates the source it generates with the GeneratedCodeAttribute. This provides metadata about the tool which generated the code, this is what we'll use as an identifier.
Note: the code snippets use:
CSharpFunctionalExtensions - to use Maybe<T> BTW - if you're reading this, and have not checked out this library, I highly recommend. You don't need to adopt the pattern 100%, treat it as a buffet and take / use what you want
XUnit, because it's better than NUnit and MSTest 🙃
Code Snippet
// Helper class
internal static class AttributeHelper
{
public static bool IsVogenValueObject(this Type targetType)
{
Maybe<GeneratedCodeAttribute> generatedCodeAttribute =
targetType.GetClassAttribute<GeneratedCodeAttribute>();
return generatedCodeAttribute.HasValue &&
generatedCodeAttribute.Value.Tool == "Vogen";
}
private static Maybe<TAttribute> GetClassAttribute<TAttribute>(
this Type targetType) where TAttribute : Attribute
{
return targetType.GetAttribute<TAttribute>();
}
}
Usage Example (From EfCore)
foreach (IMutableEntityType et in builder.Model.GetEntityTypes())
{
PropertyInfo[] properties = et.ClrType.GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.PropertyType.IsVogenValueObject())
{
// Huzzah!
// Do something with the property that is a value
// object generated by Vogen....
}
}
}
Testing
Data for unit test
[ValueObject<Guid>]
// ReSharper disable once PartialTypeWithSinglePart
public readonly partial struct VogenStronglyTypedId {}
Unit Test
```c#
public class VogenStronglyTypedIdTests
{
[Fact]
public void ShouldIdentityVogenAttributeByHelperMethod()
{
Type vogenType = typeof(VogenStronglyTypedId);
Maybe<GeneratedCodeAttribute> generatedCodeAttribute =
vogenType.GetClassAttribute<GeneratedCodeAttribute>();
Assert.True(generatedCodeAttribute.HasValue);
GeneratedCodeAttribute? valueOfAttribute =
generatedCodeAttribute.Value;
valueOfAttribute.Tool.Should().Be("Vogen");
}
[Fact]
public void ShouldIdentityVogenAttribute()
{
Type vogenType = typeof(VogenStronglyTypedId);
vogenType.IsVogenValueObject()
.Should()
.Be(true);
}
}
Summary
Jeff says:
Adding all value objects to the ModelConfigurationBuilder