Skip to content

Latest commit

 

History

History
816 lines (613 loc) · 14.8 KB

README.md

File metadata and controls

816 lines (613 loc) · 14.8 KB
Durian logo

CopyFrom allows to copy implementations of members to other members, without the need for inheritance. A regex pattern can be provided to customize the copied implementation.

Table of Contents

  1. Structure
  2. Setup
  3. Basics
    1. Allowed targets
    2. Unnamed members
    3. Generic
    4. Multiple CopyFroms
  4. Configuration
    1. Special members
    2. Partial parts
    3. Adding usings
    4. Additional nodes
  5. Patterns

Structure

Packages that are part of the CopyFrom module:

CopyFrom provides 4 types:

Setup

To start using CopyFrom, reference either the Durian.CopyFrom or Durian package.

Note: Like with other Durian modules, the target project must reference the Durian.Core package as well.

Basics

In order to copy from a member, specify a Durian.CopyFromTypeAttribute for a type...

using Durian;

[CopyFromType(typeof(Target))]
public partial class Test
{
}

public class Target
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

// Generated

public partial class Test
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

...or a Durian.CopyFromMethodAttribute for a method.

using Durian;

public partial class Test
{
	[CopyFromMethod("Init")]
	public partial void Method();

	public void Init()
	{
		Console.WriteLine("Init");
	}
}

// Generated

public partial class Test
{
	public partial void Method()
	{
		Console.WriteLine("Init");
	}
}

Note: If the method the Durian.CopyFromMethodAttribute is applied to returns void, the targetted member also has to return void, and vice versa, if the method has a return type, the targetted method also requires a return type.

Note: In case of Durian.CopyFromTypeAttribute, the target type can be specified with either the typeof() or a an actual name in form of a string (e.g. through nameof()).

using Durian;

/// Using 'typeof' is possib.e
[CopyFromType(typeof(Target))]
public partial class Test1
{
}

/// Using 'nameof' (or any other string) is also possible.
public partial class Test2
{
}

The typeof approach is should be preferred, thoguh, since it skips the necessary overhead of resolving a string into an actual type.

Allowed targets

For various reasons, not all member kinds are valid targets for CopyFrom.

For Durian.CopyFromTypeAttribute, the valid targets are:

  • Classes.
  • Structs.
  • Interfaces.
  • Records.

For Durian.CopyFromMethodAttribute, the valid targets are:

  • Non-local methods.
  • Non-auto properties.
  • Event and property accessors.
  • Indexers.
  • Constructors.
  • Operators.

Unnamed members

It is possible to target members that have no actual in-code name. This includes indexers, accessors and operators. Rules here are quite similar to C#'s <see cref=""/> tags.

Targetting indexers is possible by writing this followed by types of indexer's parameters in square brackets.

using Durian;

public partial class Test
{
	[CopyFromMethod("this[int]")]
	public partial int Method();

	public int this[int index] => 1;
}

// Generated

public partial class Test
{
	public partial int Method() => 1;
}

Targetting accessors is possible by writing the name of the member followed by '_' and keyword of the accessor.

using Durian;

public partial class Test
{
	[CopyFromMethod("Index_get]")]
	public partial int Method();

	public int Index
	{
		get => 1;
		set
		{
		}
	}
}

// Generated

public partial class Test
{
	public partial int Method() => 1;
}

The same applies to events...

using Durian;

public partial class Test
{
	[CopyFromMethod("Event_add]")]
	public partial void Method();

	public event System.Action Event
	{
		add
		{
			System.Console.WriteLine("Added");
		}
		remove
		{
			System.Console.WriteLine("Removed");
		}
	}
}

// Generated

public partial class Test
{
	public partial void Method()
	{
		System.Console.WriteLine("Added");
	}
}

...and indexers.

using Durian;

public partial class Test
{
	[CopyFromMethod("this[int]_get")]
	public partial int Method();

	public int this[int index]
	{
		get => 1;
		set
		{
		}
	}
}

// Generated

public partial class Test
{
	public partial int Method() => 1;
}

Targetting conversion operators is possible by writing implicit/explicit operator followed by the return type and source type in parenthesis.

using Durian;

public partial class Test
{
	[CopyFromMethod("implicit operator int(Test)")]
	public partial int Method();

	public static implicit operator int(Test test) => 1;
}

// Generated

public partial class Test
{
	public partial int Method() => 1;
}

Targetting non-conversion operators is possible by writting the operator keyword followed by the operator token and list of parameter types.

using Durian;

public partial class Test
{
	[CopyFromMethod("operator +(Test, Test)")]
	public partial int Method();

	public static int operator +(Test left, Test right) => 1;
}

// Generated

public partial class Test
{
	public partial int Method() => 1;
}

Generics

It is possible to copy from generic members, event if the marked member itself is not generic.

using Durian;

[CopyFromType("Target<T>"))]
public partial class Test
{
}

public class Target<T>
{
	public T Init()
	{
		Console.WriteLine("Init");
		return default(T);
	}
}

// Generated

public partial class Test
{
	public T Init()
	{
		Console.WriteLine("Init");
		return default(T);
	}
}

It is possible to copy from a member with substited arguments.

using Durian;

[CopyFromType("Target<int>"))]
public partial class Test
{
}

public class Target<T>
{
	public T Init()
	{
		Console.WriteLine("Init");
		return default(T);
	}
}

// Generated

public partial class Test
{
	public int Init()
	{
		Console.WriteLine("Init");
		return default(int);
	}
}

Multiple CopyFroms

A type can be marked with multiple Durian.CopyFromTypeAttributes.

using Durian;

[CopyFromType(typeof(Target))]
[CopyFromType(typeof(Other))]
public partial class Test
{
}

public class Target
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

public class Other
{
	public void Method()
	{
		Console.WriteLine("Me");
	}
}

// Generated

public partial class Test
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

public partial class Test
{
	public void Method()
	{
		Console.WriteLine("Me");
	}
}

By default, the attributes are resolved in order as they written in code (top-to-bottom). The ordering can be set statically using the 'Order' property.

using Durian;

[CopyFromType(typeof(Target), Order = 2)]
[CopyFromType(typeof(Other), Order = 1)]
public partial class Test
{
}

public class Target
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

public class Other
{
	public void Method()
	{
		Console.WriteLine("Me");
	}
}

// Generated

public partial class Test
{
	public void Method()
	{
		Console.WriteLine("Me");
	}
}

public partial class Test
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

Attribute with lower value of 'Order' will be resolved first. If the 'Order' values are equal, the default ordering rule applies.

At first look, the value of 'Order' does not seem to change anything, but in reality that's not the case. This information will later prove itself crutial when dealing with Durian.PatternAttributes.

Configuration

Certain aspects of how CopyFrom works can be altered by using properties present on the attributes.

Special members

Some members are automacially modified when copying, to ensure validity of the generated code.

using Durian;

[CopyFromType(typeof(Target))]
public partial class Test
{
}

public class Target
{
	public Target()
	{
	}
}

// Generated

public partial class Test
{
	// The type name 'Test' is written instead of the original 'Target'.
	public Test()
	{
	}
}

This applies to constructors, destructors and operators.

Such behaviour can be turned off by setting the 'HandleSpecialMembers' property to false.

using Durian;

[CopyFromType(typeof(Target), HandleSpecialMembers = false)]
public partial class Test
{
}

public class Target
{
	public Target()
	{
	}
}

// Generated

public partial class Test
{
	// The original 'Target' type name is preserved, leading to invalid code (at least at this step).
	public Target()
	{
	}
}

Scenario like this might be desirable when working with the Durian.PatternAttribute (on that topic later).

Partial parts

Sometimes it can be preferrable to copy only a part of type's functionality. This can be achieved in multiple ways (e.g. through proper inheritance), but by far the easiest solution is to mark the type with the Durian.PartialNameAttribute and refer to it through the 'PartialPart' property of Durian.CopyFromTypeAttribute.

using Durian;

[CopyFromType(typeof(Target), PartialPart = "TargetPartial")]
public partial class Test
{
}

public partial class Test
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

[PartialName("TargetPartial")]
public partial class Test
{
	public void Other()
	{
		Console.WriteLine("Other");
	}
}

// Generated

public partial class Test
{
	// Only members of the 'TargetPartial' part are copied.
	public void Other()
	{
		Console.WriteLine("Other");
	}
}

Adding usings

It is possible to add a using directive that is not present in the original code. To do this, use the 'AddUsings' property, present on both Durian.CopyFromTypeAttribute and Durian.CopyFromMethodAttribute.

using Durian;

[CopyFromType(typeof(Target), AddUsings = new string[] { "System" })]
public partial class Test
{
}

public class Target
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

// Generated

using System;

public partial class Test
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

This way, it is also possible to add static usings or aliases.

using Durian;

[CopyFromType(typeof(Target), AddUsings = new string[] { "System", "static System.Collections.Generic.Enumerable", "Sy = System" })]
public partial class Test
{
}

public class Target
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

// Generated

using System;
using static System.Collections.Generic.Enumerable;
using Sy = System;

public partial class Test
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

This is especially useful when using a Durian.PatternAttribute.

Additional nodes

It is possible to also copy nodes that are not normally part of the target member. This includes:

  • Using directives.
  • Attributes.
  • Base type.
  • Base interface list.
  • Type parameter constraints.
  • Documentation comments.

It can be achieved by using the 'AdditionalNodes' property, present on both Durian.CopyFromTypeAttribute and Durian.CopyFromMethodAttribute.

using Durian;
using Durian.Configuration;

[CopyFromType(typeof(Target), AdditionalNodes = CopyFromAdditionalNodes.BaseType | CopyFromAdditionalNodes.Documentation)]
public partial class Test
{
}

/// <summary>This is a target.</summary>
public class Target : System.Attribute
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

// Generated

/// <summary>This is a target.</summary>
public partial class Test : System.Attribute
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

By default, only CopyFromAdditionalNodes.Usings is specified.

Patterns

The CopyFrom feature would not be as useful as it is without a way do replace parts of the copied code. This is exactly where the Durian.PatternAttribute comes to help.

using Durian;

[CopyFromType(typeof(Target))]
[Pattern("Init", "Method")]
public partial class Test
{
}

public class Target
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

// Generated

public partial class Test
{
	public void Method()
	{
		Console.WriteLine("Method");
	}
}

Durian.PatternAttribute uses regular expressions to get the job done, so opportunities here are quite powerful.

using Durian;

[CopyFromType(typeof(Target))]
[Pattern("void (\w+)", "int $1_copied")]
public partial class Test
{
}

public class Target
{
	public void Init1()
	{
		Console.WriteLine("Init1");
	}

	public void Init2()
	{
		Console.WriteLine("Init2");
	}
}

// Generated

public partial class Test
{
	public int Init1_copied()
	{
		Console.WriteLine("Init1");
	}

	public int Init2_copied()
	{
		Console.WriteLine("Init2");
	}
}

Patterns can also be chained, with the 'Order' property specifying the actual order.

using Durian;

[CopyFromType(typeof(Target))]
[Pattern("Init", "Method", Order = 1)]
[Pattern("Method", "Me", Order = 2)]
public partial class Test
{
}

public class Target
{
	public void Init()
	{
		Console.WriteLine("Init");
	}
}

// Generated

public partial class Test
{
	public void Me()
	{
		Console.WriteLine("Method");
	}
}

(Written by Piotr Stenke)