BunnyTail.CommonCode

June 7, 2026 ยท View on GitHub

NuGet

Reference

Add reference to BunnyTail.CommonCode to csproj.

  <ItemGroup>
    <PackageReference Include="BunnyTail.CommonCode" Version="1.2.0" />
  </ItemGroup>

ToString

Source

[GenerateToString]
public partial class Data
{
    public int Id { get; set; }

    public string Name { get; set; } = default!;

    public int[] Values { get; set; } = default!;

    [IgnoreToString]
    public int Ignore { get; set; }
}

Result

var data = new Data { Id = 123, Name = "xyz", Values = [1, 2] };
var str = data.ToString();
Assert.Equal("{ Id = 123, Name = xyz, Values = [1, 2] }", str);

Property attributes

AttributeDescription
[IgnoreToString]Exclude the property from output
[ToStringFormat(format)]Apply a format string (AppendFormatted(value, format))
[ToStringMaxLength(length)]Truncate the stringified value to the given length
[ToStringMask] / [ToStringMask(Show = n)]Mask the value; with Show, reveal only the last n characters

When combined, the priority is Mask > MaxLength > Format (MaxLength and Format can be combined). These attributes apply to scalar properties; collection-valued properties are emitted as before. null values follow the configured null literal.

[GenerateToString]
public partial class User
{
    public int Id { get; set; }

    [ToStringMask]
    public string Password { get; set; } = default!;     // Password = ***

    [ToStringMask(Show = 2)]
    public string Token { get; set; } = default!;        // Token = ***34

    [ToStringFormat("yyyy-MM-dd")]
    public DateTime BirthDate { get; set; }              // BirthDate = 2020-01-02

    [ToStringMaxLength(20)]
    public string Description { get; set; } = default!;  // truncated to 20 chars
}

Diagnostics

IDSeverityDescription
BTCC0101WarningType must be partial

Equality

Generates IEquatable<T>, Equals, GetHashCode, and optional equality operators.

Source

[GenerateEquality]
public partial class OrderData
{
    public int Id { get; init; }
    public string Name { get; init; } = default!;

    [IgnoreEquality]
    public DateTime UpdatedAt { get; init; }
}

// Deep collection comparison enabled, operators generated
[GenerateEquality(GenerateOperators = true, DeepCollectionEquality = true)]
public sealed partial class TaggedData
{
    public string Name { get; init; } = default!;
    public string[] Tags { get; init; } = [];
}

Attribute options

PropertyDefaultDescription
GenerateOperatorstrueEmit == and != operators
DeepCollectionEqualityfalseUse SequenceEqual for collection properties

Equality and hash code are computed from all reachable public properties, including those inherited from base types (flattened). base.Equals / base.GetHashCode are not called.

Result

var a = new OrderData { Id = 1, Name = "x" };
var b = new OrderData { Id = 1, Name = "x", UpdatedAt = DateTime.Now };
Assert.True(a.Equals(b)); // UpdatedAt is ignored

Diagnostics

IDSeverityDescription
BTCC0201WarningType must be partial
BTCC0202WarningNo public properties found for equality comparison

CompareTo

Generates IComparable<T> and relational operators using properties marked with [CompareKey].

Source

[GenerateCompareTo]
public partial class PersonData
{
    [CompareKey(Order = 1)]
    public string LastName { get; init; } = default!;

    [CompareKey(Order = 2)]
    public string FirstName { get; init; } = default!;

    public int Age { get; init; }
}

Attribute options

PropertyDefaultDescription
GenerateOperatorstrueEmit <, >, <=, >= operators

Result

var a = new PersonData { LastName = "Adams", FirstName = "Alice" };
var b = new PersonData { LastName = "Zorn",  FirstName = "Bob"   };
Assert.True(a < b);

Diagnostics

IDSeverityDescription
BTCC0501WarningType must be partial
BTCC0502WarningNo [CompareKey] properties found

DeepClone

Generates a DeepClone() method. The target type must implement IDeepCloneable<T>.

Source

[GenerateDeepClone]
public partial class DocumentData : IDeepCloneable<DocumentData>
{
    public string Title { get; set; } = default!;
    public List<string> Tags { get; set; } = new();
    public int[] Scores { get; set; } = [];
    public AuthorData Owner { get; set; } = default!;

    [ShallowClone]   // copy reference as-is
    public object? ExtraRef { get; set; }

    [CloneIgnore]    // omit from clone entirely
    public int CacheKey { get; set; }
}

[GenerateDeepClone]
public partial class AuthorData : IDeepCloneable<AuthorData>
{
    public string Name { get; set; } = default!;
}

Clone strategy per property type

TypeStrategy
Value type / stringDirect copy
IDeepCloneable<T>.DeepClone()
ArrayArray.Clone()
List<T>new List<T>(original)
Other referenceShallow (with [ShallowClone])

Result

var clone = doc.DeepClone();
clone.Tags.Add("new");
Assert.Equal(2, doc.Tags.Count);  // original unchanged

Diagnostics

IDSeverityDescription
BTCC0301WarningType must be partial
BTCC0302WarningType must implement IDeepCloneable<T>
BTCC0303WarningProperty type does not support deep clone; use [ShallowClone] to suppress

DelegateTo

Generates forwarding members that delegate method and property calls to an attributed field or property.

Source

public interface ISimpleService
{
    string GetMessage();
    void Reset();
    int Count { get; set; }
}

[GenerateDelegateTo]
public partial class LoggingService : ISimpleService
{
    [DelegateTo]
    private readonly SimpleServiceImpl _inner = new();
}

Result

ISimpleService svc = new LoggingService();
svc.Count = 5;
Assert.Equal("Hello-5", svc.GetMessage());

The generator will not emit a member if the containing type already defines it, allowing manual overrides.

Diagnostics

IDSeverityDescription
BTCC0401WarningType must be partial
BTCC0402WarningNo [DelegateTo] field or property found
BTCC0403WarningInterfaceType must be an interface implemented by the delegate member type