TA.SourceGenerators.CompositionTypes 1.3.0

dotnet add package TA.SourceGenerators.CompositionTypes --version 1.3.0
NuGet\Install-Package TA.SourceGenerators.CompositionTypes -Version 1.3.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TA.SourceGenerators.CompositionTypes" Version="1.3.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TA.SourceGenerators.CompositionTypes --version 1.3.0
#r "nuget: TA.SourceGenerators.CompositionTypes, 1.3.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install TA.SourceGenerators.CompositionTypes as a Cake Addin
#addin nuget:?package=TA.SourceGenerators.CompositionTypes&version=1.3.0

// Install TA.SourceGenerators.CompositionTypes as a Cake Tool
#tool nuget:?package=TA.SourceGenerators.CompositionTypes&version=1.3.0

Composition

This source generator library aims to introduce a limited version of "multiple inheritance" and "struct inheritance" by using composition.

How to install

Add the following to your SDK-style project file:

	<ItemGroup>
		<PackageReference Include="TA.SourceGenerators.Composition" Version="1.3.0" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
		<PackageReference Include="TA.SourceGenerators.CompositionTypes" Version="1.3.0" />
	</ItemGroup>

Keep in mind that since this is ISourceGenerator-based, VS performance may be negatively impacted if there are many projects; it's highly not recommended to apply this using Directory.Build.props or any other similar mechanism; it should only be added to the projects where it's needed.

Usage

Imagine you have a struct like this:

[CompositionPart]
public struct Point2D : IEquatable<Point2D>
{
  public Point2D(double x, double y)
  {
    this.X = x;
    this.Y = y;
  }
  public double X, Y;

  public bool Equals(Point2D other) => this.X == other.X && this.Y == other.Y;
  public Point2D Add(Point2D obj) => new(this.X + obj.X, this.Y + obj.Y);
  public override bool Equals(object? obj) => obj is Point2D p && this.Equals(p);

  public override int GetHashCode() => this.X.GetHashCode() ^ this.Y.GetHashCode();
}

Note the optional [CompositionPart] attribute - it triggers the generation of an interface like below:

public interface IPoint2DComposition : IComposedOf<Point2D>, IEquatable<Point2D>
{
  double X { get; set; }
  double Y { get; set; }
  Point2D Add(Point2D obj);
}

Then, if you want to "inherit" Point2D:

[ComposedOf(typeof(Point2D))] 
public partial struct Point3D : IEquatable<Point3D>
{
  public Point3D(double x, double y, double z)
  {
    this.BasePoint2D(x, y);
    this.Z = z;
  }
  public double Z;
  public bool Equals(Point3D obj) => this.Point2D.Equals(obj.Point2D) && this.Z == obj.Z;
}

The [ComposedOf(...)] is the main attribute which is required to generate "compositions" like below:

#pragma warning disable CS0282 // There is no defined ordering between fields in multiple declarations of partial struct
partial struct Point3D : IEquatable<Point2D>, IPoint2DComposition
#pragma warning restore CS0282 // There is no defined ordering between fields in multiple declarations of partial struct
{
  // Base type: Point2D
  public Point2D Point2D;
  public static implicit operator Point2D(Point3D obj) => obj.Point2D;
  Library1.Point2D IComposedOf<Library1.Point2D>.Base => this.Point2D;
  bool IEquatable<Point2D>.Equals(Point2D obj) => this.Point2D.Equals(obj);
  // Constructors
  [MemberNotNull(nameof(Point2D))]
  public void BasePoint2D(double x, double y) => this.Point2D = new Point2D(x, y);
  // Members: Point2D
  public double X { get => this.Point2D.X; set => this.Point2D.X = value; }
  public double Y { get => this.Point2D.Y; set => this.Point2D.Y = value; }
  public System.Boolean Equals(Library1.Point2D other) => this.Point2D.Equals(other);
  public Library1.Point2D Add(Library1.Point2D obj) => this.Point2D.Add(obj);

  public  bool BaseEquals(object? obj)
  {
    if (obj is Library1.Point2D point2DLocal)
      return this.Point2D.Equals(point2DLocal);
    return base.Equals(obj);
  }
}

You are then able to do things like below:

var p3 = new Point3D(1, 2, 3);
Point2D p2 = p3; // p2.X == 1, p2.Y == 2; p3.Z was lost
//p3 = (Point3D)p2; // compile error - no conversion exists (unless defined manually)
double x = p3.X; // same as p3.Point2D.X
bool b = p3.Equals(p2); // same as p3.Point2D.Equals(p2) 
p2 = p3.Add(p2); // same as p3.Point2D.Add(p2); now p2.X = 2, p2.Y = 4
object p3Boxed = p3;
var p2Equatable = (IEquatable<Point2D>)p3Boxed; // works - because we "lifted" the interfaces
var p2Composition = (IPoint2DComposition)p3Boxed; // this interface is implemented, allowing us to invoke Point2D members
var p2Composition2 = (IComposedOf<Point2D>)p3Boxed; // if [CompositionPart] is not added to Point2D, then this generic interface can be used

Due to the limitation on generic arguments being saved as attribute parameters, an overload is available - [ComposedOf(Type, string)]:

public class Class2D<T2> { ..... }

[ComposedOf(typeof(Class2D<>), "<T3>")]
public partial class Class3DGeneric<T3>
{
  public Class3DGeneric(...)
  {
    this.BaseClass2D(...);
    // ....
  }
}
Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 is compatible. 
.NET Standard netstandard2.1 is compatible. 
.NET Framework net48 is compatible.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETCoreApp 3.1

    • No dependencies.
  • .NETFramework 4.8

    • No dependencies.
  • .NETStandard 2.1

    • No dependencies.
  • net5.0

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.3.0 94 2/22/2024