^^^ ^^^ Data annotation Attributes
All custom attributes must inherit from the System.Attribute
class. This is the foundation that allows the attribute to be recognized by the .NET runtime.
- The attribute class must end with the name
Attribute
- The attribute class must be annotated with the
AttributeUsage
attribute, which determines how a custom attribute class can be used.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class MyCustomAttribute : Attribute
{
public string Description { get; }
public int Version { get; }
// Constructor to initialize the attribute
public MyCustomAttribute(string description, int version)
{
Description = description;
Version = version;
}
}
In the above example the MyCustomAttribute
is restricted to be used on methods and classes. The Inherited = false
prevents the attribute from being inherited by derived classes. The AllowMultiple = false
ensures that the attribute can only be applied once per element.
It is used to mark program elements (like classes, methods, properties, etc.) as outdated or no longer recommended for use. The [Obsolete]
attribute is typically applied to warn developers when they use a deprecated feature.
[Obsolete("Use NewMethod instead.")]
public static void OldMethod()
{
Console.WriteLine("This is the old method.");
}
During compile if any method calls the OldMethod()
code, then a compiler warning will be produced with the message Use NewMethod instead.
Available since C# 12. It is used to indicate an experimental feature. The compiler issues a warning if you access a method or type annotated with the [Experimental]
attribute. It is used to communicate that the feature is in a trial phase and not yet stable.
The [SetsRequiredMembers]
attribute informs the compiler that a constructor sets all required
members in that class or struct. The compiler assumes any constructor with the SetsRequiredMembers
attribute initializes all required
members.
The [ModuleInitializer]
attribute marks a method that the runtime calls when the assembly loads. The ModuleInitializer attribute can only be applied to a method that:
- Is static.
- Is parameterless.
- Returns void.
- Is accessible from the containing module, that is, internal or public.
- Isn't a generic method.
- Isn't contained in a generic class.
- Isn't a local function.
The ModuleInitializer attribute can be applied to multiple methods. In that case, the order in which the runtime calls them is deterministic but not specified.
using System.Runtime.CompilerServices;
internal static class Program
{
[ModuleInitializer]
public static void BeforeMain()
{
Console.WriteLine("This is executed before main");
}
private static void Main(string[] args)
{
Console.WriteLine("Main");
}
}
The [OverloadResolutionPriority]
attribute enables library authors to prefer one overload over another when two overloads can be ambiguous. Its primary use case is for library authors to write better performing overloads while still supporting existing code without breaks.
[OverloadResolutionPriority(1)]
public void M(params ReadOnlySpan<int> s) => Console.WriteLine("Span");
// Default overload resolution priority of 0
public void M(params int[] a) => Console.WriteLine("Array");
Overload resolution considers the two methods equally good for some argument types. For an argument of int[]
, it prefers the first overload. To get the compiler to prefer the ReadOnlySpan
version, you can increase the priority of that overload. This way, when the user types in M(
into the editor, the void M(params ReadOnlySpan<int> s)
version will be suggested by the IntelliSense first.
In a nullable enabled context, the compiler performs static analysis of code to determine the null-state of all reference type variables:
- not-null: Static analysis determines that a variable has a non-null value.
- maybe-null: Static analysis can't determine that a variable is assigned a non-null value.
Attributes can be aplied to our code that gives the compiler more information about the rules for your API. When calling code is compiled in a nullable enabled context, the compiler will warn callers when they violate those rules. These attributes don't enable more checks on your implementation.
AllowNull
: A non-nullable parameter, field, or property may be null.DisallowNull
: A nullable parameter, field, or property should never be null.
[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();
[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;
MaybeNull
: A non-nullable parameter, field, property, or return value may be null.NotNull
: A nullable parameter, field, property, or return value will never be null.
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
{
//implementation
}
public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
_ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
}
NotNullWhen
: A nullable argument won't be null when the method returns the specifiedbool
value.MaybeNullWhen
: A non-nullable argument may be null when the method returns the specifiedbool
value.NotNullIfNotNull
: A return value, property, or argument isn't null if the argument for the specified parameter isn't null.
bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message is not null;
}
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
{
//implementation
}
MemberNotNull
: The listed member won't be null when the method returns.MemberNotNullWhen
: The listed member won't be null when the method returns the specified bool value.
public class Container
{
private string _uniqueIdentifier; // must be initialized.
private string? _optionalMessage;
public Container()
{
Helper();
}
public Container(string message)
{
Helper();
_optionalMessage = message;
}
[MemberNotNull(nameof(_uniqueIdentifier))]
private void Helper()
{
_uniqueIdentifier = DateTime.Now.Ticks.ToString();
}
}
DoesNotReturn
: A method or property never returns. In other words, it always throws an exception.DoesNotReturnIf
: A method or property never returns if the associated bool parameter has the specified value.
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}
public void SetState(object containedField)
{
if (containedField is null)
{
FailFast();
}
// containedField can't be null:
_field = containedField;
}