DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

C# Object Initialization Like a Pro: From Inline Blocks to Explicit Assignments (Basic Expert)

C# Object Initialization Like a Pro: From Inline Blocks to Explicit Assignments (Basic → Expert)

C# Object Initialization Like a Pro: From Inline Blocks to Explicit Assignments (Basic → Expert)

You don’t just “new up” an object in C# — you decide how expressive, safe, and readable that initialization will be. Whether you’re building a DTO from Graph API or wiring repositories, your initialization style matters.

Here’s your tiered playbook.


🟢 BASIC — “Just get me an object”

Inline initializer block (concise, declarative):

var defaultCallee = new ParticipantEndpoint
{
    OdataType = "#microsoft.graph.callRecords.participantEndpoint",
    UserAgent = new UserAgent
    {
        OdataType = "#microsoft.graph.callRecords.clientUserAgent",
        HeaderValue = null,
        ApplicationVersion = null,
        AdditionalData =
        {
            ["Platform"] = "unknown",
            ["ProductFamily"] = "unknown",
            ["CommunicationServiceId"] = null,
            ["AzureADAppId"] = null
        }
    },
    AdditionalData = { ["Identity"] = null }
};
Enter fullscreen mode Exit fullscreen mode

✅ Benefits:

  • Compact, declarative.
  • Great for simple DTOs, tests, mocks.
  • Less boilerplate.

⚠️ Caveat:

  • Harder to debug line-by-line in watch windows.
  • Nested object initializers can become dense.

🟡 MEDIUM — “Step by step, please”

Explicit assignments (procedural, verbose):

var defaultCallee = new ParticipantEndpoint();
defaultCallee.OdataType = "#microsoft.graph.callRecords.participantEndpoint";
defaultCallee.UserAgent = new UserAgent();
defaultCallee.UserAgent.OdataType = "#microsoft.graph.callRecords.clientUserAgent";
defaultCallee.UserAgent.HeaderValue = null;
defaultCallee.UserAgent.ApplicationVersion = null;
defaultCallee.UserAgent.AdditionalData["Platform"] = "unknown";
defaultCallee.UserAgent.AdditionalData["ProductFamily"] = "unknown";
defaultCallee.UserAgent.AdditionalData["CommunicationServiceId"] = null;
defaultCallee.UserAgent.AdditionalData["AzureADAppId"] = null;
defaultCallee.AdditionalData["Identity"] = null;
Enter fullscreen mode Exit fullscreen mode

✅ Benefits:

  • Debug-friendly: you can set breakpoints after each assignment.
  • Easier when conditional logic is needed.
  • Works well in step-through tutorials or when building incrementally.

⚠️ Caveat:

  • Verbose and repetitive.
  • Can hide intent under “noise.”

🔵 ADVANCED — “Design for maintainability”

When to choose one over the other?

  • Inline initializer: Use when the object is a “data bag” (DTO, record, config snapshot). Perfect for declarative intent.
  • Explicit assignment: Use when initialization involves conditions, loops, validations, or services. Perfect for imperative workflows.

Example (mixed):

var endpoint = new ParticipantEndpoint
{
    OdataType = "#microsoft.graph.callRecords.participantEndpoint",
    UserAgent = new UserAgent
    {
        OdataType = "#microsoft.graph.callRecords.clientUserAgent"
    }
};

// Add conditionals later
if (isUnknownPlatform)
    endpoint.UserAgent.AdditionalData["Platform"] = "unknown";

if (!string.IsNullOrEmpty(version))
    endpoint.UserAgent.ApplicationVersion = version;
Enter fullscreen mode Exit fullscreen mode

🟣 EXPERT — “Object initialization is OOP design”

It’s not just syntax sugar — it’s how your model communicates intent:

  • Immutability: prefer record types with init-only setters when data must not change after construction.
  • Encapsulation: hide complex initialization in factory methods or builders.
  • Performance: fewer allocations when using initializer blocks vs. multiple statements.
  • Readability: initializer blocks group related data; assignments spread across code.

👉 Example with a factory:

public static ParticipantEndpoint CreateDefault(string? version = null)
    => new()
    {
        OdataType = "#microsoft.graph.callRecords.participantEndpoint",
        UserAgent = new UserAgent
        {
            OdataType = "#microsoft.graph.callRecords.clientUserAgent",
            ApplicationVersion = version,
            AdditionalData =
            {
                ["Platform"] = "unknown",
                ["ProductFamily"] = "unknown"
            }
        },
        AdditionalData = { ["Identity"] = null }
    };
Enter fullscreen mode Exit fullscreen mode

Now your construction logic is reusable, testable, and intention-revealing.


⚡ Cheat Sheet

Goal Use Example
Quick, concise DTO Inline initializer new Foo { A=1, B=2 }
Debug / conditional setup Explicit assignments foo.A=1; if(x) foo.B=2;
Enforce invariants Factory or Builder Foo.CreateDefault()
Immutability record with init public record Foo { int A { get; init; } }

🧠 Mental Model

Think of object initialization in C# as levels of intent:

  1. Declare data shape → inline initializer.
  2. Assemble step by step → explicit assignment.
  3. Encapsulate complexity → factory/builder patterns.

Pick the tool that matches your debuggability, readability, and design goals.


Written by: Cristian Sifuentes – Full-stack dev crafting scalable apps with [.NET - Azure], [Angular - React], Git, SQL & AI integrations. Dark mode, clean code, and atomic commits enthusiast.

✨ Final Thought:

Initializer blocks and assignments aren’t competitors — they’re complementary. Use inline blocks when declaring state; use assignments when narrating logic. The expert move? Wrap them both in clean abstractions.

Top comments (0)