Microsoft has just launched the .NET 8 Preview 2, introducing a host of new features that are sure to pique developersā interest. In this post, weāll delve into these new additions and improvements that this release brings to the .NET ecosystem.
RequiredAttribute.DisallowAllDefaultValues[]
One of the first new features introduced in the .NET 8 preview 2 is the RequiredAttribute.DisallowAllDefaultValues
functionality. The RequiredAttribute
is used to mark a property or parameter as required, meaning it cannot be null.
With this new update, the DisallowAllDefaultValues
property has been added to the RequiredAttribute
, which allows validation that a struct does not equal its default value.
For instance, suppose we have a property in a class that is of type Guid
. By default, a Guid
is initialized to Guid.Empty
. To ensure that the value of this property is always set, we can use the Required
attribute with the DisallowAllDefaultValues
property set to true
.
If the value of this property is not explicitly set and it remains as Guid.Empty
, the validation will fail, indicating that the value must be provided. Hereās an example of how to use this functionality:
public class MyClass
{
[Required(DisallowAllDefaultValues = true)]
public Guid MyGuidValue { get; set; }
}
In the above code, the MyGuidValue
property is marked with the Required
attribute, and the DisallowAllDefaultValues
property is set to true
. Therefore, the property will be validated to ensure that it is not equal to Guid.Empty
. If the property is not set or is explicitly set to Guid.Empty
, the validation will fail.
This feature is especially useful in scenarios where it is essential to ensure that the value of a property or parameter is set explicitly and cannot be left as its default value. It adds an extra layer of safety to the code and can help catch bugs early in the development cycle.
RangeAttribute exclusive bounds
This feature allows users to specify exclusive bounds in their range validation using the RangeAttribute
. With this, users can define ranges with boundaries that are excluded from validation.
To illustrate, consider the following code example:
[Range(0d, 1d, MinimumIsExclusive = true, MaximumIsExclusive = true)]
public double Sample { get; set; }
In this example, the RangeAttribute
is applied to a double
property called Sample
. The attribute specifies that the valid range for Sample
is any value greater than 0
and less than 1
. However, the boundary values 0
and 1
are excluded from the valid range.
By setting MinimumIsExclusive
and MaximumIsExclusive
to true
, the RangeAttribute
creates a range that is open at both ends, meaning that values that fall on the boundary of the range will fail validation. For instance, a value of 0
or 1
will not pass validation for the Sample
property.
This is useful for scenarios where users want to define a range that is open at both ends, such as when defining probability values or ranges of values that cannot be exactly reached. Users can define more precise and nuanced validation rules for their data.
LengthAttribute
Another improvement in .NET 8 preview 2 is an update to the LengthAttribute
.
This attribute can now be used to set both lower and upper bounds for strings or collections, providing more flexibility in validation.
To use this updated attribute, simply specify the minimum and maximum allowed lengths as arguments to the LengthAttribute
, as shown in the following example:
[Length(10, 20)] // Require at least 10 elements and at most 20 elements.
public ICollection<int> Values { get; set; }
As you can see in the code example above, this ensures that the Values
collection must contain at least 10 elements and no more than 20 elements. If the collection fails to meet either of these criteria, the validation will fail.
This is particularly useful in scenarios where developers need to enforce specific length constraints on strings or collections in their application. With the LengthAttribute
, itās now easier than ever to add this type of validation to your code.
AllowedValuesAttribute and DeniedValuesAttribute
The following features that Microsoft presents are two new attributes that can be used for validating a property against a list of allowed or denied values.
These attributes are called AllowedValuesAttribute
and DeniedValuesAttribute
.
-
AllowedValuesAttribute
specifies a list of allowed values -
DeniedValuesAttribute
specifies a list of denied values.
Hereās an example of how to use these attributes:
[AllowedValues("chocolate", "vanilla", "strawberry")]
public string IceCreamFlavor { get; set; }
[DeniedValues("beetroot", "eggplant", "rutabaga")]
public string CakeFlavor { get; set; }
In this example, the IceCreamFlavor
property can only be set to āchocolateā, āvanillaā, or āstrawberryā, while the CakeFlavor
property cannot be set to ābeetrootā, āeggplantā, or ārutabagaā. If the property is set to a value not in the allowed list or in the denied list, the validation will fail.
These attributes can be useful in scenarios where you want to restrict the possible values of a property, such as in a dropdown list or a set of checkboxes.
System.Reflection Enhancements
Microsoft added support for obtaining function pointer metadata via Reflection in .NET 8 Preview 2. This feature allows developers to obtain parameter types, return type, and calling conventions for function pointers. Previously, only the IntPtr
type was used for function pointers, which made obtaining function pointer metadata difficult.
The new functionality is currently available in the CoreCLR runtime and in MetadataLoadContext
. Support for the Mono and NativeAOT runtimes is expected later.
Hereās an example using reflection:
FieldInfo fieldInfo = typeof(MyClass).GetField(nameof(MyClass._fp));
Type fpType = fieldInfo.FieldType;
Console.WriteLine(fpType.IsFunctionPointer); // True
Console.WriteLine(fpType.IsUnmanagedFunctionPointer); // True
Console.WriteLine($"Return type: {fpType.GetFunctionPointerReturnType()}");
foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
{
Console.WriteLine($"Parameter type: {parameterType}");
}
Type modifiedType = fieldInfo.GetModifiedFieldType();
Type normalType = modifiedType.UnderlyingSystemType;
foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
{
Console.WriteLine($"Calling convention: {callConv}");
}
foreach (Type modreq in modifiedType.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers())
{
Console.WriteLine($"Required modifier for first parameter: {modreq }");
}
public unsafe class MyClass
{
public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}
This feature supports parameterized types such as generics, pointers, and arrays, including an array of function pointers. The Type.ElementType
property and the Type.GetGenericArguments()
method can be used to obtain further types which ultimately may be a function pointer.
As .NET 8 continues to evolve, we can expect to see even more advancements in the platform that will allow developers to build powerful and efficient applications. What are your thoughts on the future of .NET 8 and the direction in which Microsoft is taking the platform?