Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ There are two components: a **Roslyn source generator** that emits companion `Ex
| `UseMemberBody` | [Reference →](https://efnext.github.io/reference/use-member-body) |
| Roslyn analyzers & code fixes (EFP0001–EFP0012) | [Reference →](https://efnext.github.io/reference/diagnostics) |
| Limited/Full compatibility mode | [Reference →](https://efnext.github.io/reference/compatibility-mode) |
| Polymorphic dispatch (hierarchies) | [Advanced →](https://efnext.github.io/advanced/polymorphic-dispatch) |

## FAQ

Expand Down
87 changes: 87 additions & 0 deletions docs/advanced/polymorphic-dispatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Polymorphic Dispatch (Hierarchies)

EF Core Projectables supports abstract/virtual/overwritten properties and methods decorated with `[Projectable]`, and can generate expression trees to mimic virtual calls.

## Runtime

Virtual members are invoked for the most-specific type of the instance.

```csharp
public class Foo{
public virtual string Name() => "Foo";
}

public class Bar : Foo{
public override string Name() => "Bar";
}


Foo bar = new Bar();
bar.Name(); // "Bar"
```

## Expressions

Expressions are compiled and cannot know which type the provided instance will be, so the only solution is a type test chain, which gets automatically created.

```csharp
public class Foo{
[Projectable(PolymorphicDispatch = true)]
public virtual string Name() => "Foo";
// Converted to: @this is Bar ? "Bar" : "Foo"
}

public class Bar : Foo{
[Projectable(PolymorphicDispatch = true)]
public override string Name() => "Bar";
// Converted to: "Bar" as it has no derived types
}
```

## Abstract Members

Members can also be abstract, in which case the last branch of the type test chain will just be the last type itself.

```csharp
public abstract class Foo{
[Projectable(PolymorphicDispatch = true)]
public abstract string Name();
// Converted to: @this is Bar ? "Bar" : "Baz"
}

public class Bar : Foo{
[Projectable(PolymorphicDispatch = true)]
public override string Name() => "Bar";
// Converted to: "Bar" as it has no derived types
}

public class Baz : Foo{
[Projectable(PolymorphicDispatch = true)]
public override string Name() => "Baz";
// Converted to: "Baz" as it has no derived types
}
```

## Base Invocations

You can also use base in your derived types to invoke the base method/property.

```csharp
public class Foo{
[Projectable(PolymorphicDispatch = true)]
public virtual string Name() => "Foo";
// Converted to: @this is Bar ? (((Bar)@this).MyProp ? "Bar" : "Foo") : "Foo"
}

public class Bar : Foo{
public bool MyProp { get; set; }

[Projectable(PolymorphicDispatch = true)]
public override string Name() => MyProp ? "Bar" : base.Name();
// Converted to: @this.MyProp ? "Bar" : "Foo" as it has no derived types
}
```

## Enabling Polymorphic Dispatch

Add `PolymorphicDispatch = true` to the Projectables
18 changes: 18 additions & 0 deletions docs/guide/projectable-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,24 @@ public string GetStatus(decimal threshold)

See [Block-Bodied Members](/advanced/block-bodied-members) for full details.

## Polymorphic Dispatch (Hierarchies)

```csharp
public class Foo{
[Projectable(PolymorphicDispatch = true)]
public virtual string Name() => "Foo";
// Converted to: @this is Bar ? "Bar" : "Foo"
}

public class Bar : Foo{
[Projectable(PolymorphicDispatch = true)]
public override string Name() => "Bar";
// Converted to: "Bar" as it has no derived types
}
```

See [Polymorphic Dispatch](/advanced/polymorphic-dispatch) for full details.

## Important Rules

- Methods must be **expression-bodied** (`=>`) unless `AllowBlockBody = true`.
Expand Down
18 changes: 18 additions & 0 deletions docs/guide/projectable-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ public string Category

See [Block-Bodied Members](/advanced/block-bodied-members) for the full feature documentation.

## Polymorphic Dispatch (Hierarchies)

```csharp
public class Foo{
[Projectable(PolymorphicDispatch = true)]
public virtual string Name => "Foo";
// Converted to: @this is Bar ? "Bar" : "Foo"
}

public class Bar : Foo{
[Projectable(PolymorphicDispatch = true)]
public override string Name => "Bar";
// Converted to: "Bar" as it has no derived types
}
```

See [Polymorphic Dispatch](/advanced/polymorphic-dispatch) for full details.

## Important Rules

- The property **must be expression-bodied** (using `=>`) unless `AllowBlockBody = true` is set.
Expand Down
36 changes: 36 additions & 0 deletions docs/reference/projectable-attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,31 @@ See [Block-Bodied Members](/advanced/block-bodied-members) for full details.

---

### `PolymorphicDispatch`

**Type:** `bool`
**Default:** `false`

Enables **polymorphic dispatch** support for abstract/virtual/overwritten members.

```csharp
public class Foo{
[Projectable(PolymorphicDispatch = true)]
public virtual string Name() => "Foo";
// Converted to: @this is Bar ? "Bar" : "Foo"
}

public class Bar : Foo{
[Projectable(PolymorphicDispatch = true)]
public override string Name() => "Bar";
// Converted to: "Bar" as it has no derived types
}
```

See [Polymorphic Dispatch](/advanced/polymorphic-dispatch) for full details.

---

## Complete Example

```csharp
Expand Down Expand Up @@ -144,6 +169,17 @@ public class Order
return "Normal";
}
}

// Polymorphic Dispatch
[Projectable(PolymorphicDispatch = true)]
public virtual string Currency() => "EUR";
// Converted to: @this is Preorder ? "USD" : "EUR"
}

public class Preorder : Order{
[Projectable(PolymorphicDispatch = true)]
public override string Currency() => "USD";
// Converted to: "USD" as it has no derived types
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,10 @@ public sealed class ProjectableAttribute : Attribute
/// Set this to true to suppress the experimental feature warning.
/// </remarks>
public bool AllowBlockBody { get; set; }

/// <summary>
/// Get or set whether to inline derived types overrides of the member.
/// </summary>
public bool PolymorphicDispatch { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
Condition="'$(Projectables_ExpandEnumMethods)' == ''" />
<Projectables_AllowBlockBody
Condition="'$(Projectables_AllowBlockBody)' == ''" />
<Projectables_PolymorphicDispatch
Condition="'$(Projectables_PolymorphicDispatch)' == ''" />
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="Projectables_NullConditionalRewriteSupport" />
<CompilerVisibleProperty Include="Projectables_ExpandEnumMethods" />
<CompilerVisibleProperty Include="Projectables_AllowBlockBody" />
<CompilerVisibleProperty Include="Projectables_PolymorphicDispatch" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static internal class Diagnostics
public readonly static DiagnosticDescriptor RequiresBodyDefinition = new DiagnosticDescriptor(
id: "EFP0006",
title: "Method or property should expose a body definition",
messageFormat: "Method or property '{0}' should expose a body definition (e.g. an expression-bodied member or a block-bodied method) to be used as the source for the generated expression tree.",
messageFormat: "Method or property '{0}' should expose a body definition (e.g. an expression-bodied member or a block-bodied method) to be used as the source for the generated expression tree. If the member is abstract you can use the InlineHierarchy property on the Projectable attribute to allow an empty body which will be replaced by the derived classes' implementations.",
category: "Design",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
Expand Down
Loading