Strings are one of the most universal data types. We use them for URLs or regular expressions or even to define some date. With .NET 7 we have a new way of giving those strings a bit of meaning. Meet StringSyntaxAttribute
.
I also show you a way how to use them in .NET 6 and earlier.
StringSyntaxAttribute
The StringSyntaxAttribute
is a new edition introduced in .NET 7. The goal is to have a unified way of telling what data we expect in a given string
(or ReadOnlySpan<char
for that matter). Visual Studio since ages provides you help with regular expressions for example. But this function was built in and there was no way of telling: "Hey my API here expects a regex string". So Microsoft introduced that concept with .NET 7.
To use this function simply annotate your function with the given attribute on the parameter:
void SomeRegex([StringSyntax(StringSyntaxAttribute.Regex)] string regex) { }
In Rider and Visual Studio as soon as you pass in the string, the IDE helps you. Not only with regex but with other formats as well like the composite format (string.Format
highlighting) or how to format a DateTime
object.
As you can see you can declare a lot of different syntax types:
void SomeRegex([StringSyntax(StringSyntaxAttribute.Regex)] string regex) { }
void SomeDate([StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string dateTime) { }
void SomeFormat(
[StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format,
params object[] args) { }
In the API documentation, you find all possible values.
Making it work with .NET 6 and earlier
The following describes how you can make this approach work even with .NET 6. I tested this only in Rider but it works in Visual Studio as well.
Luckily the compiler does not really care where this attribute is coming from. It is enough that the attribute in the correct namespace is present. So we can utilize that behavior and can declare the attribute on our own. Again: The namespace used is important. It has to be the same as the "original" one.
#if !NET7_0_OR_GREATER
// The namespace is important
namespace System.Diagnostics.CodeAnalysis;
/// <summary>Fake version of the StringSyntaxAttribute, which was introduced in .NET 7</summary>
public sealed class StringSyntaxAttribute : Attribute
{
/// <summary>The syntax identifier for strings containing composite formats.</summary>
public const string CompositeFormat = nameof(CompositeFormat);
/// <summary>The syntax identifier for strings containing regular expressions.</summary>
public const string Regex = nameof(Regex);
/// <summary>The syntax identifier for strings containing date information.</summary>
public const string DateTimeFormat = nameof(DateTimeFormat);
/// <summary>
/// Initializes a new instance of the <see cref="StringSyntaxAttribute"/> class.
/// </summary>
public StringSyntaxAttribute(string syntax)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="StringSyntaxAttribute"/> class.
/// </summary>
public StringSyntaxAttribute(string syntax, params object?[] arguments)
{
}
}
#endif
You might notice two things here. First, we have a #ifdef
. The sole purpose is to avoid a clash if we target or upgrade to .NET 7 at some point and then the same attribute does exist two times. This is especially important if you target multiple .NET frameworks (see the sample code at the end).
The second thing is that I only declare Regex
, DateTimeFormat
and CompositeFormat
even though the original list holds more entries. Well, it is simple: I am lazy and I only needed those three entries for my demo.
Conclusion
The new attribute can make your life and the life of your consumers easier. I do like this addition and use it on several occasions in my own API, including the fallback I showed earlier.