Skip to content

Instantly share code, notes, and snippets.

@soraphis
Created February 18, 2023 11:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soraphis/69f0411a42e50295496d89db09965bce to your computer and use it in GitHub Desktop.
Save soraphis/69f0411a42e50295496d89db09965bce to your computer and use it in GitHub Desktop.
C# trait interface implementation proposal

Disclaimer: ChatGPT helped me to write this markdown file

Proposal for a C# Language Feature: Trait Implementations

Summary

This proposal introduces a new language feature for C# called Trait Implementations. This feature allows developers to provide implementations for interfaces on types that they don't have access to. Trait Implementations can also be used to extend existing interfaces with default implementations.

Motivation

In C#, if a developer wants to implement an interface on a type, they must have access to the source code of that type. However, in some cases, a developer may want to add functionality to a type that they don't have access to, such as a third-party library. This can be achieved by creating a wrapper class that implements the interface and delegates to the original type, but this can be cumbersome and add unnecessary complexity.

Trait Implementations provide a simpler way to add functionality to types that a developer doesn't have access to. This can improve code maintainability and reduce boilerplate code.

Syntax

The syntax for defining a Trait Implementation is similar to that of implementing an interface:

impl InterfaceName for TypeName
{
    // implementation
}

The impl keyword is used to define the Trait Implementation, followed by the name of the interface to be implemented and the name of the type to implement it on. The implementation itself is contained within curly braces.

Trait Implementations can also be used to extend existing interfaces with default implementations. This is done by using the where keyword to specify a constraint on the generic type parameter:

impl TraitName for IAlreadyExistingInterface
{
    // implementation
}

Examples

Here are some examples of how Trait Implementations could be used:

Example 1: Implementing an Interface on a Third-Party Type

// define interface, that should act as trait
interface IActivatable
{
    void SetActive(bool b);
}

// "trait implementation" for types you don't have access to.
impl IActivatable for Behaviour
{
    void SetActive(bool b) => this.enabled = b;
} 

This example shows how a developer can implement the IActivatable interface on a third-party type Behaviour using a Trait Implementation. The implementation simply sets the enabled property of the Behaviour instance to the value passed to the SetActive method.

Example 2: Trait implementation with Generic constraint

// "trait implementation" to extend an existing interface with a default implementation.
interface IInterpolateable<T>
{
    T Lerp(T from, T to, float t);
}

impl IInterpolateable<Matrix4x4> for Matrix4x4 
{
    Matrix4x4 Lerp(Matrix4x4 from, Matrix4x4 to, float t)
    {
        return Matrix4x4.TRS(
            Vector3.Lerp(from.transpose, to.transpose, t),
            Quaternion.Lerp(from.rotation, to.rotation, t),
            Vector3.Lerp(from.lossyScale, to.lossyScale, t)
        );
    }
}

Example 3: Extending an Existing Interface

// "trait implementation" to extend a generic interface with a default implementation.
impl IInterpolateable<INumber<T>> for INumber<T> where T : INumber<T>
{
    T Lerp(T from, T to, float t) => (1 - t) * from + t * to;    
}

This example shows how a developer can extend the IInterpolateable interface with a default implementation for any type that implements the INumber<T> interface. The implementation uses the * and + operators to interpolate the values of the numbers using the Lerp method.

Design Considerations

Method Resolution

If a Trait Implementation provides a default implementation for an interface member, that implementation will be used when calling the member through a cast to the interface type. If the method is called through an instance of the target type, the explicit implementation provided by the target type will be used.

class A
{
  public void MyMethod();
}

impl IMyTrait for A
{
  public void MyMethod(){ ... }
}

A a = new A();
IMyTrait ia = a;
ia.MyMethod(); // Uses Trait Implementation
a.MyMethod(); // Uses A.MyMethod()

Inheritance

Any type that derives from a base type that has a Trait Implementation for an interface will automatically inherit the Trait Implementation for that interface. This means that any type B is considered to have a Trait T as long as there is a Trait Implementation for T defined for B itself or any of its base types.

For example, if a Trait Implementation is defined for IMyTrait on class A, then any type that derives from A, such as class B : A, will also use the Trait Implementation for IMyTrait, even if it does not have its own Trait Implementation.

A a = new A();
B b = new B();
Assert.IsTrue(a is IMyTrait);
Assert.IsTrue((b as A) is IMyTrait);
Assert.IsTrue(b is IMyTrait);

Conclusion

Trait Implementations provide a flexible and powerful way for developers to add functionality to types that they don't have access to. They can also be used to extend existing interfaces with default implementations, which can improve code maintainability and reduce boilerplate code. The syntax for defining a Trait Implementation is simple and intuitive, and the design of the feature is straightforward and easy to understand. This feature would be a valuable addition to the C# language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment