Skip to content

CompileFast returns wrong values when a member is accessed on an interface converted to a struct #553

Description

@danielmarbach

When a lambda converts an interface-typed value to a struct and then accesses a member, CompileFast() returns a different result than Compile(). Depending on the member it returns the struct itself, a default value, or the value of a different member. Converting from object instead of from an interface works, and converting an interface to a class works too.

Repro

FastExpressionCompiler 5.4.1 (latest release), .NET 10 (SDK 10.0.401), macOS arm64.

using System.Linq.Expressions;
using FastExpressionCompiler;

Check<IHasId>("interface -> struct, string property", x => ((Start)x).Id, new Start());
Check<object>("object -> struct, string property", x => ((Start)x).Id, new Start());
Check<IHasId>("interface -> struct, int property", x => ((Start)x).Number, new Start());
Check<IHasId>("interface -> struct, field", x => ((Start)x).Field, new Start());
Check<IHasId>("interface -> struct, method call", x => ((Start)x).GetId(), new Start());
Check<IHasId>("interface -> class, string property", x => ((StartClass)x).Id, new StartClass());

static void Check<T>(string name, Expression<Func<T, object>> expression, T argument)
{
    object system = expression.Compile()(argument);
    object fast = expression.CompileFast()(argument);
    var verdict = Equals(system, fast) ? "same" : "DIFFERENT";
    Console.WriteLine($"{verdict,-9} {name}: Compile()={system} ({system.GetType().Name}), CompileFast()={fast} ({fast.GetType().Name})");
}

public interface IHasId;

public struct Start : IHasId
{
    public Start() { }

    public string Id { get; set; } = "s";
    public int Number { get; set; } = 42;
    public string Field = "f";
    public string GetId() => Id;
}

public class StartClass : IHasId
{
    public string Id { get; set; } = "c";
}

Output

DIFFERENT interface -> struct, string property: Compile()=s (String), CompileFast()=Start (Start)
same      object -> struct, string property: Compile()=s (String), CompileFast()=s (String)
DIFFERENT interface -> struct, int property: Compile()=42 (Int32), CompileFast()=0 (Int32)
DIFFERENT interface -> struct, field: Compile()=f (String), CompileFast()=s (String)
DIFFERENT interface -> struct, method call: Compile()=s (String), CompileFast()=Start (Start)
same      interface -> class, string property: Compile()=c (String), CompileFast()=c (String)

Expected

The same results as Compile(): s, 42, f and s.

Context

We ran into this in NServiceBus, where saga mappings like m => ((Start)m).Id on an interface-typed message are compiled with CompileFast(). From the outside it looks like the unbox is missing when the source type is an interface, since the object case works, but I haven't looked at the emitted IL to confirm that.

Thanks for maintaining FastExpressionCompiler!

Activity

  1. added this to the v6.0.0 milestone on Oct 7, 2026
  2. dadhi commented on Oct 7, 2026

    @dadhi
    Owner

    Thank you, fixing...

  3. dadhi commented on Oct 7, 2026

    @dadhi
    Owner
  4. danielmarbach commented on Oct 8, 2026

    @danielmarbach
    Author

    THank you!

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions