Skip to content

Latest commit

 

History

History
156 lines (129 loc) · 4.61 KB

partial-constructor-injection.md

File metadata and controls

156 lines (129 loc) · 4.61 KB

Partial constructor injection

C# 14 partial constructors let a type declare its construction contract in one part and provide the body in another. Pure.DI sees the combined constructor symbol, resolves its parameters once, and invokes it like a regular constructor. This is useful when a source generator owns the defining declaration while application code supplies the implementation.

using Shouldly;
using Pure.DI;

DI.Setup(nameof(Composition))
    .Bind<IAuditTransport>("durable").To<FileAuditTransport>()
    .Bind().To(_ => new AuditSinkOptions(BatchSize: 128))

    // Composition root
    .Root<AuditSink>("AuditSink");

var composition = new Composition();
var sink = composition.AuditSink;

sink.Transport.ShouldBeOfType<FileAuditTransport>();
sink.BatchSize.ShouldBe(128);

interface IAuditTransport;

class FileAuditTransport : IAuditTransport;

record AuditSinkOptions(int BatchSize);

abstract class AuditSinkBase(IAuditTransport transport)
{
    public IAuditTransport Transport { get; } = transport;
}

partial class AuditSink : AuditSinkBase
{
    // This defining declaration participates in constructor lookup.
    // Its parameter attributes are merged with the implementing part.
    public partial AuditSink(
        [Tag("durable")] IAuditTransport transport,
        AuditSinkOptions options);

    public int BatchSize { get; private set; }
}

partial class AuditSink
{
    // A base/this initializer is allowed only on the implementing declaration.
    public partial AuditSink(
        IAuditTransport transport,
        AuditSinkOptions options)
        : base(transport)
    {
        BatchSize = options.BatchSize;
    }
}
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

A partial constructor must have exactly one defining declaration ending with ; and one implementing declaration with a body. Only the defining declaration participates in lookup, while constructor and parameter attributes from both parts are combined. Place this(...) or base(...) constructor initializers on the implementing declaration. OrdinalAttribute, TagAttribute, and OverloadResolutionPriorityAttribute can be placed on either part and are observed through the combined Roslyn symbol. This split is what lets a generator publish a construction contract while the hand-written part keeps the body, and the generator sees a private field assignment it cannot otherwise express. Both parts must live in the same assembly and be compiled together, so the pattern does not cross project boundaries.

Note

Prefer an ordinary constructor when one file owns both parts; the partial form earns its complexity only when two sources must each contribute half of it.

The following partial class will be generated
partial class Composition
{
  public AuditSink AuditSink
  {
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get
    {
      AuditSinkOptions transientAuditSinkOptions = new AuditSinkOptions(BatchSize: 128);
      return new AuditSink(new FileAuditTransport(), transientAuditSinkOptions);
    }
  }
}

Class diagram:

---
 config:
  class:
   hideEmptyMembersBox: true
---
classDiagram
	FileAuditTransport --|> IAuditTransport : "durable"
	AuditSinkOptions --|> IEquatableᐸAuditSinkOptionsᐳ
	Composition ..> AuditSink : AuditSink AuditSink
	AuditSink *-- FileAuditTransport : "durable" IAuditTransport
	AuditSink *-- AuditSinkOptions : AuditSinkOptions
	namespace Pure.DI.UsageTests.Attributes.PartialConstructorScenario {
		class AuditSink {
				<<class>>
			+AuditSink(IAuditTransport transport, AuditSinkOptions options)
		}
		class AuditSinkOptions {
				<<record>>
		}
		class Composition {
		<<partial>>
		+AuditSink AuditSink
		}
		class FileAuditTransport {
				<<class>>
			+FileAuditTransport()
		}
		class IAuditTransport {
			<<interface>>
		}
	}
	namespace System {
		class IEquatableᐸAuditSinkOptionsᐳ {
			<<interface>>
		}
	}
Loading

See also: