Skip to content

BlackWhiteYoshi/InlineComposition

Repository files navigation

InlineComposition

A source generator that merges the content of other classes into one class. A simple workaround for struct inheritance or multiple inheritance.

InlineComposition Example

  • Inlined get members of type field, property, event and method (including constructor and finalizer).
  • Attributes and summaries of the inlined members get also inlined.
  • Inheritance and implements declaration are also inlined.
  • Mixing classes and structs works fine (inline struct in class and vice versa).



Type Hierarchy / Polymorphism

Inheritance gives not only all the content of a base class, you get also a type hierarchy and polymorphism. If you need a type hierarchy or polymorphism, you can use interfaces to get the same functionality.

using InlineCompositionAttributes;

public interface IBaseA { ... }

public interface IBaseB { ... }

[InlineBase]
public class BaseA : IBaseA { ... }

[InlineBase]
public class BaseB : IBaseB { ... }

[Inline<BaseA, BaseB>]
public partial class Derived { ... }


[...]


Derived derived = new();
Console.WriteLine(derived is BaseA);  // False
Console.WriteLine(derived is BaseB);  // False
Console.WriteLine(derived is IBaseA); // True
Console.WriteLine(derived is IBaseB); // True

// polymorphism IBaseA
ExampleA(derived);

// polymorphism IBaseB
ExampleB(derived);


static void ExampleA(IBaseA baseA) { ... }

static void ExampleB(IBaseB baseB) { ... }


[...]


// <auto-generated/>
public partial class Derived : IBaseA, IBaseB { ... }



Conflicts

Members with the same identifier get merged to a single Member. Make sure merged members have the same signature. Method-bodies are merged together that every method is executed one after another.

using InlineCompositionAttributes;

[InlineBase]
public class BaseA
{
    public int MyProperty { get; set; }

    public void MyMethod()
    {
        int a = 5;
        Console.WriteLine(a);
    }
}

[InlineBase]
public class BaseB
{
    public int MyProperty { get; set; }

    public void MyMethod()
    {
        int a = 5;
        Console.WriteLine(a + a);
    }
}

[Inline<BaseA, BaseB>]
public partial class Derived { ... }


[...]


// <auto-generated/>
public partial class Derived
{
    public int MyProperty { get; set; }

    public void MyMethod()
    {
        {
        int a = 5;
        Console.WriteLine(a);
        }

        {
        int a = 5;
        Console.WriteLine(a + a);
        }
    }
}



Generic classes

Inlining of generic classes is supported. However, inserting the concrete type in place of the type parameter is done by simple text replacement. There might be situations where things get replaced/not replaced where it should/should not. So when inlining generic classes, better double check the result.

using InlineCompositionAttributes;

[InlineBase]
public class Base<T>
{
    public T MyProperty { get; set; }
}

[Inline<Base<int>>]
public partial class Derived { ... }


[...]


// <auto-generated/>
public partial class Derived
{
    public int MyProperty { get; set; }
}



Attributes

  • InlineAttribute

Generates a class with the same name and fills it with the content of the classes/structs in the type arguments.
If you inline a class/struct that has no InlineBaseAttribute, it will be ignored.

using InlineCompositionAttributes;

[Inline<Example>] // "Example" must have a InlineBaseAttribute, otherwise it has no effect.
public partial class MyClass { ... }


[...]


// <auto-generated/>
public partial class MyClass { ... }

This class comes with up to 12 type parameters. If you need more, you can easily create your own suitable one.

namespace InlineCompositionAttributes;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
internal sealed class InlineAttribute<T1, ..., TN> : Attribute { }



  • InlineBaseAttribute

In order Inline works as expected, the classes/structs to inline must be decorated with InlineBase.

using InlineCompositionAttributes;

[InlineBase] // Itself it does nothing, but this class can now be used as type argument in InlineAttribute.
public class MyClass { ... }

IgnoreInheritenceAndImplements

If this flag is set to true, the base classes and interfaces of the inlined class are ignored.
If you want inline classes that inherit from different base classes, you can use this to avoid to inherit multiple classes.

using InlineCompositionAttributes;

public abstract class BaseA { ... }

public abstract class BaseB { ... }

[InlineBase(IgnoreInheritenceAndImplements = true)]
public class InlineA : BaseA { ... }

[InlineBase]
public class InlineB : BaseB { ... }

[Inline<InlineA, InlineB>]
public partial class Derived { ... }


[...]


// <auto-generated/>
public partial class Derived : BaseB { ... }



  • InlineMethodAttribute

Overriding a normal method and adding your own code is native not possible. The InlineMethodAttribute gives support for adding code to the inlined method. The content of the decorated method is added to the inlined method specified in the MethodName parameter and takes the same parameters.

using InlineCompositionAttributes;

[InlineBase]
public class Base : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("base");
    }
}

[Inline<Base>]
public partial class Derived
{
    [InlineMethod(MethodName = "Dispose")]
    public void DisposePartial()
    {
        Console.WriteLine("derived");
    }
}


[...]


// <auto-generated/>
public partial class Derived : IDisposable
{
    [InlineMethod(MethodName = "Dispose")]
    public void Dispose()
    {
        {
        Console.WriteLine("base");
        }
        {
        Console.WriteLine("derived");
        }
    }
}

MethodName

The MethodName is a required parameter that specify the name of the method to add content to. Make sure the parameters are the same to target the right (overloaded) method.

using InlineCompositionAttributes;

[InlineBase]
public class Base
{
    public void PrintSum(int a, int b)
    {
        Console.WriteLine(a + b);
    }
}

[Inline<Base>]
public partial class Derived
{
    [InlineMethod(MethodName = "PrintSum(int, int)")]
    public void PrintSumPartial(int a, int b)
    {
        Console.WriteLine(b + a);
    }
}


[...]


// <auto-generated/>
public partial class Derived
{
    [InlineMethod(MethodName = "PrintSum(int, int)")]
    public void PrintSum()
    {
        {
        Console.WriteLine(a + b);
        }
        {
        Console.WriteLine(b + a);
        }
    }
}

Modifiers

When inlining your method-body in a method, the summary, attributes and modifiers are overwritten with your method. If you want different modifiers inlined than your method, you can use the Modifiers parameter. A common scenario is that your method should not be available (private), but the inlined method should still be public.

using InlineCompositionAttributes;

[InlineBase]
public class Base : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("base");
    }
}

[Inline<Base>]
public partial class Derived
{
    [InlineMethod(MethodName = "Dispose", Modifiers = "public")]
    private void DisposePartial()
    {
        Console.WriteLine("derived");
    }
}


[...]


// <auto-generated/>
public partial class Derived : IDisposable
{
    [InlineMethod(MethodName = "Dispose", Modifiers = "public")]
    public void Dispose()
    {
        {
        Console.WriteLine("base");
        }
        {
        Console.WriteLine("derived");
        }
    }
}

First

When the added code must run before the inlined code, you can set the First parameter to true.

using InlineCompositionAttributes;

[InlineBase]
public class Base : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("base");
    }
}

[Inline<Base>]
public partial class Derived
{
    [InlineMethod(MethodName = "Dispose", First = true)]
    public void DisposePartial()
    {
        Console.WriteLine("derived");
    }
}


[...]


// <auto-generated/>
public partial class Derived : IDisposable
{
    [InlineMethod(MethodName = "Dispose")]
    public void Dispose()
    {
        {
        Console.WriteLine("derived");
        }
        {
        Console.WriteLine("base");
        }
    }
}



  • InlineConstructorAttribute

The InlineConstructorAttribute lets you add code to the inlined constructor. The content of the decorated method is added to the inlined constructor.

using InlineCompositionAttributes;

[InlineBase]
public class Base
{
    public Base()
    {
        Console.WriteLine("base");
    }
}

[Inline<Base>]
public partial class Derived
{
    [InlineConstructor]
    public void ConstructorPartial()
    {
        Console.WriteLine("derived");
    }
}


[...]


// <auto-generated/>
public partial class Derived
{
    [InlineConstructor]
    public Derived()
    {
        {
        Console.WriteLine("base");
        }
        {
        Console.WriteLine("derived");
        }
    }
}

Modifiers

The same functionality as InlineMethodAttribute.Modifiers

First

The same functionality as InlineMethodAttribute.First



  • InlineFinalizerAttribute

The InlineFinalizerAttribute lets you add code to the inlined finalizer. The content of the decorated method is added to the inlined finalizer.

[InlineBase]
public class Base
{
    ~Base()
    {
        Console.WriteLine("base");
    }
}

[Inline<Base>]
public partial class Derived
{
    [InlineFinalizer]
    public void FinalizerPartial()
    {
        Console.WriteLine("derived");
    }
}


[...]


// <auto-generated/>
public partial class Derived
{
    [InlineFinalizer]
    ~Derived()
    {
        {
        Console.WriteLine("base");
        }
        {
        Console.WriteLine("derived");
        }
    }
}

First

The same functionality as InlineMethodAttribute.First



  • NoInlineAttribute

Members decorated with this attribute are ignored.

using InlineCompositionAttributes;

[InlineBase]
public class MyBase
{
    [NoInline]
    private int myField;

    public int MyProperty { get; set; }
}

[Inline<MyBase>]
public partial class Derived { ... }


[...]


// <auto-generated/>
public partial class Derived
{
    public int MyProperty { get; set; }
}



Disable Attribute Generation

You can disable the generation of the attributes by defining a constant for your compilation:

  <PropertyGroup>
    <DefineConstants>INLINECOMPOSITION_EXCLUDE_ATTRIBUTES</DefineConstants>
  </PropertyGroup>

This functionality is specific for the use case when you have a project referencing another project, both projects using this generator and you have InternalsVisibleTo enabled. In that case you have the attributes defined twice in your referencing project and you get a warning about that. By defining this constant in your referencing project, you prevent one generation, so the attributes are only defined once in the referenced project.

About

A source generator that merges the content of other classes into one class. A simple workaround for struct inheritance or multiple inheritance.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages