Category: Interface2Null

[Interface2NullObject]Debugger and Converter–part 5

 

It will be interesting to see the Null Object in the debugger with properties generated from interface -see  https://www.nuget.org/packages/rscg_Interface_to_null_object

 

This is not very difficult to generate once you have the interface with properties

The code for generating

public string DebuggerDisplay()  
{
    StringBuilder sb = new StringBuilder("[System.Diagnostics.DebuggerDisplay(");
    sb.Append("\"");
    foreach (var item in props)
    {
        sb.Append(item.Name);
        sb.Append(" = {");
        sb.Append(item.Name);
        sb.Append("} " );
    }
    sb.Append("\")]");
    return sb.ToString();
}

 

and the code generated is

 

[System.Diagnostics.DebuggerDisplay("FirstName = {FirstName} LastName = {LastName} Department = {Department} ")]
public partial class Employee_null : global::IntegrationConsole.IEmployee

 

More interesting is to have a converter in order to obtain from the JSON string the interface, not the object .

 

The code for generating is

 

public class @(Model.Name + "Converter") : System.Text.Json.Serialization.JsonConverter<@(Model.FullName)>
    {
    public override @Model.FullName Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
    {
    // Deserialize the JSON to the concrete type @Model.Name
    return System.Text.Json.JsonSerializer.Deserialize<@nameClass>
        (ref reader, options);
        }

        public override void Write(System.Text.Json.Utf8JsonWriter writer, @Model.FullName value, System.Text.Json.JsonSerializerOptions options)
        {
        // Serialize the concrete type @Model.Name
        System.Text.Json.JsonSerializer.Serialize(writer, (@nameClass)value, options);
        }
        }


and the code generated is

//serialize and deserialize
var empString = JsonSerializer.Serialize(employee);
Console.WriteLine(empString);
//deserialize

var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    DefaultBufferSize = 128
};
options.Converters.Add(new IDepartmentConverter());
options.Converters.Add(new IEmployeeConverter());

var emp2 = JsonSerializer.Deserialize<IEmployee>(empString,options);
ArgumentNullException.ThrowIfNull(emp2);
Console.WriteLine(emp2.FirstName);
Console.WriteLine(emp2.Department.Name);
Debug.Assert(emp2.FirstName == "Andrei");

[Interface2NullObject]Replace default-part 4

So for https://github.com/ignatandrei/rscg_Interface_to_null_object we put the default return value as the default for the return type. For properties, it is enough easy to modify the data.  But for functions ?  Let’s say we return an array

public IEmployee[] Employees();

The default value is null – so it will be throwing an error when do “foreach ” .

So – I should think about a way to register default values for different methods.
I have the idea to register into the .csproj – so here it is

<ItemGroup>
	<CompilerVisibleProperty Include="I2NO_String" />
	<CompilerVisibleProperty Include="I2NO_IntegrationConsole_IEmployee_Array" />
	<CompilerVisibleProperty Include="I2NO_System_Collections_Generic_IAsyncEnumerable_Of_IntegrationConsole_IEmployee_EndOf" />
</ItemGroup>
<ItemGroup>
  <PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
<PropertyGroup>
	<I2NO_String>return ""</I2NO_String>
	<I2NO_IntegrationConsole_IEmployee_Array>return []</I2NO_IntegrationConsole_IEmployee_Array>
	<I2NO_System_Collections_Generic_IAsyncEnumerable_Of_IntegrationConsole_IEmployee_EndOf>return AsyncEnumerable.Empty_Of_IntegrationConsole.IEmployee_EndOf();</I2NO_System_Collections_Generic_IAsyncEnumerable_Of_IntegrationConsole_IEmployee_EndOf>
</PropertyGroup>

So this is the result

        public virtual string Name { get; set; } = default(string);
    
        public virtual IntegrationConsole.IEmployee[] Employees() {  return [] ; }
    
        public virtual System.Collections.Generic.IAsyncEnumerable<IntegrationConsole.IEmployee> EmployeesAsync() {  return AsyncEnumerable.Empty<IntegrationConsole.IEmployee>() ; }

[Interface2NullObject]Examples–part 3

Examples for rscg_Interface_to_null_object: Simplifying the Null Object Pattern

Now I can show some examples for rscg_Interface_to_null_object. This project aims to simplify the implementation of the Null Object Pattern in C# by automatically generating null object classes from interfaces.

I will start with those 2 interfaces:


using InterfaceToNullObject;

namespace IntegrationConsole;
[ToNullObject]
public interface IDepartment
{
    public string Name { get; set; }
}

and

using InterfaceToNullObject;

namespace IntegrationConsole;
[ToNullObject]
public interface IEmployee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public IDepartment Department { get; set; }
    public string GetFullName();

    public string GetFullNameAndDepartment(string separator);
    public bool MoveEmployeeToDepartment(IDepartment department);

}

The generated code is the following

// <auto-generated>
    //     This code was generated by a tool :rscg_Interface_to_null_object
    //     Runtime Version: José Saramago is feeling diplomatic in Bissau
    //     DateOfTool : 2025-01-20 16:28:25
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    // </auto-generated>
//------------------------------------------------------------------------------
/// <summary>
    /// This static partial class contains extension methods for sorting collections of IDepartment objects.
    /// </summary>

 #nullable enable
 #pragma warning disable CS8603
 #pragma warning disable CS8625
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.CodeDom.Compiler.GeneratedCode("GeneratorName","2025.10120.11628.125")]
public partial class Department_null : global::IntegrationConsole.IDepartment
{

        public virtual string Name { get; set; } = default(string);
    
}

#nullable restore
#pragma warning restore CS8603
#pragma warning restore CS8625

And the employee

// <auto-generated>
    //     This code was generated by a tool :rscg_Interface_to_null_object
    //     Runtime Version: José Saramago is feeling diplomatic in Bissau
    //     DateOfTool : 2025-01-20 16:28:25
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    // </auto-generated>
//------------------------------------------------------------------------------
/// <summary>
    /// This static partial class contains extension methods for sorting collections of IEmployee objects.
    /// </summary>

 #nullable enable
 #pragma warning disable CS8603
 #pragma warning disable CS8625
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.CodeDom.Compiler.GeneratedCode("GeneratorName","2025.10120.11628.125")]
public partial class Employee_null : global::IntegrationConsole.IEmployee
{

        public virtual string FirstName { get; set; } = default(string);
    
        public virtual string LastName { get; set; } = default(string);
    
        public virtual IntegrationConsole.IDepartment Department { get; set; } = default(IntegrationConsole.IDepartment);
    
        public virtual string GetFullName() { return default(string); }
    
        public virtual string GetFullNameAndDepartment(string separator) { return default(string); }
    
        public virtual bool MoveEmployeeToDepartment(global::IntegrationConsole.IDepartment department) { return default(bool); }
    
}

#nullable restore
#pragma warning restore CS8603
#pragma warning restore CS8625

So please checkout rscg_Interface_to_null_object.

[Interface2NullObject]Implementation–part2

Introducing rscg_Interface_to_null_object: Simplifying the Null Object Pattern

I’m excited to announce the release of my new project, rscg_Interface_to_null_object. This project aims to simplify the implementation of the Null Object Pattern in C# by automatically generating null object classes from interfaces.

Architecture Overview

The project is composed of two main components:

  1. Attribute Project: This project contains the attribute that you can apply to interfaces for which you want to generate null object classes.
  2. Generator Project: This project contains the logic for intercepting the interfaces with the attribute and generating the corresponding null object classes.

How It Works

Intercepting the Attribute

The generator project first intercepts the attribute applied to the interfaces. This allows it to identify which interfaces need null object classes.

        var classesToApplySortable = context.SyntaxProvider.ForAttributeWithMetadataName(
"InterfaceToNullObject.ToNullObjectAttribute",
IsAppliedOnInterface,
FindAttributeDataExpose
)
.Collect()
.SelectMany((data, _) => data.Distinct())
.Collect();

Generating Classes

Once the interfaces are identified, the generator uses a RazorBlade template to create the null object classes. This ensures that the generated classes are consistent and follow best practices.

@inherits RazorBlade.PlainTextTemplate<rscg_Interface_to_null_object.DataFromExposeInterface>
@{
    string nameClass = Model.Name.StartsWith("I")?Model.Name.Substring(1):Model.Name;
    nameClass += "_null";
}
// <auto-generated>
    //     This code was generated by a tool :@Generated.rscg_Interface_to_null_object.TheAssemblyInfo.AssemblyName
    //     Runtime Version: @Generated.rscg_Interface_to_null_object.TheAssemblyInfo.GeneratedNameNice
    //     DateOfTool : @Generated.rscg_Interface_to_null_object.TheAssemblyInfo.DateGeneratedUTC.ToString("yyyy-MM-dd HH:mm:ss")
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    // </auto-generated>
//------------------------------------------------------------------------------
/// <summary>
    /// This static partial class contains extension methods for sorting collections of @Model.Name objects.
    /// </summary>

 #nullable enable
 #pragma warning disable CS8603
 #pragma warning disable CS8625
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.CodeDom.Compiler.GeneratedCode("GeneratorName","@Generated.rscg_Interface_to_null_object.TheAssemblyInfo.DateGeneratedUTC.ToString("yyyy.1MMdd.1HHmm.1ss")")]
public partial class @(nameClass) : @Model.FullName
{
@foreach(var prop in Model.props){
    <text>
        public virtual @prop.Type @prop.Name { get; set; } = default(@prop.Type);
    </text>
}
@foreach (var func in Model.functions)
{
    <text>
        public virtual @func.ReturnType @Model.DisplayFunc(func) { return default(@func.ReturnType); }
    </text>
}

}

#nullable restore
#pragma warning restore CS8603
#pragma warning restore CS8625

Getting Started

To get started with rscg_Interface_to_null_object, you can add the NuGet packages to your project and apply the attribute to your interfaces. The generator will take care of the rest, creating the necessary null object classes for you.

For more details and examples, check out the GitHub repository.

Happy coding!

[Interface2NullObject] Idea- part 1

Every time I kick off a new project, I dive straight into classes/methods that do stuff – because who doesn’t love instant gratification? But now, I’m turning over a new leaf. From now on, I’m starting with interfaces and then gradually building out the classes that make the magic happen.

But here’s the catch – for every interface, I need a class with the same properties and methods, even if it just sits there doing nothing. Think of it like the https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.abstractions.nulllogger?view=net-9.0-pp or the Null Object Pattern.

So, how do I automate this? Enter Roslyn Source Code Generators! Roslyn will hand me the interface, methods, and properties on a silver platter, and I’ll have the class in no time!

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.