Skip to content

Latest commit

 

History

History
199 lines (168 loc) · 5.23 KB

custom-attributes.md

File metadata and controls

199 lines (168 loc) · 5.23 KB

Custom attributes

To use custom attributes, derive from System.Attribute and register them with the setup API:

  • TagAttribute
  • OrdinalAttribute
  • TypeAttribute You can also use combined attributes. Each registration method can take an optional argument index (default is 0) that specifies where to read tag, ordinal, or type metadata. Use this when a codebase must not reference Pure.DI types in its domain code, or when the built-in names clash with another framework's attributes.
using Shouldly;
using Pure.DI;

DI.Setup(nameof(PersonComposition))
    .TagAttribute<MyTagAttribute>()
    .OrdinalAttribute<MyOrdinalAttribute>()
    .TypeAttribute<MyTypeAttribute>()
    .TypeAttribute<MyGenericTypeAttribute<TT>>()
    .Arg<int>("personId")
    .Bind().To(() => new Uri("https://github.com/DevTeam/Pure.DI"))
    .Bind("NikName").To(() => "Nik")
    .Bind().To<Person>()

    // Composition root
    .Root<IPerson>("Person");

var composition = new PersonComposition(personId: 123);
var person = composition.Person;
person.ToString().ShouldBe("123 Nik https://github.com/DevTeam/Pure.DI");

[AttributeUsage(
    AttributeTargets.Constructor
    | AttributeTargets.Method |
    AttributeTargets.Property |
    AttributeTargets.Field)]
class MyOrdinalAttribute(int ordinal) : Attribute;

[AttributeUsage(
    AttributeTargets.Parameter
    | AttributeTargets.Property
    | AttributeTargets.Field)]
class MyTagAttribute(object tag) : Attribute;

[AttributeUsage(
    AttributeTargets.Parameter
    | AttributeTargets.Property
    | AttributeTargets.Field)]
class MyTypeAttribute(Type type) : Attribute;

[AttributeUsage(
    AttributeTargets.Parameter
    | AttributeTargets.Property
    | AttributeTargets.Field)]
class MyGenericTypeAttribute<T> : Attribute;

interface IPerson;

class Person([MyTag("NikName")] string name) : IPerson
{
    private object? _state;

    [MyOrdinal(1)] [MyType(typeof(int))] internal object Id = "";

    [MyOrdinal(2)]
    public void Initialize([MyGenericType<Uri>] object state) =>
        _state = state;

    public override string ToString() => $"{Id} {name} {_state}";
}
Running this code sample locally
dotnet --list-sdk
  • Create a net10.0 (or later) console application
dotnet new console -n Sample
dotnet add package Pure.DI
dotnet add package Shouldly
  • Copy the example code into the Program.cs file

You are ready to run the example 🚀

dotnet run

Note

Custom attributes provide extensibility for advanced scenarios where standard attributes don't meet specific requirements.

The registration is what makes an attribute meaningful: without .TagAttribute<T>(), .OrdinalAttribute<T>() or .TypeAttribute<T>() the generator never reads it, and the members simply do not participate. Register the attributes that were actually declared in that assembly. The registration is per composition, so a library that declares its own attributes also ships the registrations that make them work. An attribute that is registered in one composition but not in another produces different graphs from the same code.

Tip

Prefer one attribute class that carries tag and ordinal together (see custom universal attribute) over three narrowly scoped ones when the same member often needs both.

The following partial class will be generated
partial class PersonComposition
{
  private readonly int _argPersonId;

  [OrdinalAttribute(128)]
  public PersonComposition(int personId)
  {
    _argPersonId = personId;
  }

  public IPerson Person
  {
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get
    {
      string transientString = "Nik";
      Uri transientUri = new Uri("https://github.com/DevTeam/Pure.DI");
      var transientPerson = new Person(transientString);
      transientPerson.Id = _argPersonId;
      transientPerson.Initialize(transientUri);
      return transientPerson;
    }
  }
}

Class diagram:

---
 config:
  class:
   hideEmptyMembersBox: true
---
classDiagram
	Uri --|> IEquatableᐸUriᐳ
	Uri --|> ISpanFormattable
	Uri --|> ISerializable
	Person --|> IPerson
	PersonComposition ..> Person : IPerson Person
	Person o-- Int32 : Argument "personId"
	Person *-- Uri : Uri
	Person *-- String : "NikName" String
	namespace Pure.DI.UsageTests.Attributes.CustomAttributesScenario {
		class IPerson {
			<<interface>>
		}
		class Person {
				<<class>>
			+Person(String name)
			~Object Id
			+Initialize(Object state) : Void
		}
		class PersonComposition {
		<<partial>>
		+IPerson Person
		}
	}
	namespace System {
		class IEquatableᐸUriᐳ {
			<<interface>>
		}
		class Int32 {
				<<struct>>
		}
		class ISpanFormattable {
			<<interface>>
		}
		class String {
				<<class>>
		}
		class Uri {
				<<class>>
		}
	}
	namespace System.Runtime.Serialization {
		class ISerializable {
			<<interface>>
		}
	}
Loading

See also: