.NET 8 Preview 2 Unveiled: 5 New Features You Need to Know

Mar 17, 2023 | .NET

Although a few weeks ago Microsoft released .NET 8 Preview 1, now 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?

You May Also Like

Sign up For Our Newsletter

Weekly .NET Capsules: Short reads for busy devs.

  • NLatest .NET tips and tricks
  • NQuick 5-minute reads
  • NPractical code snippets
.