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!
When a lambda converts an interface-typed value to a struct and then accesses a member,
CompileFast()returns a different result thanCompile(). Depending on the member it returns the struct itself, a default value, or the value of a different member. Converting fromobjectinstead 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.
Output
Expected
The same results as
Compile():s,42,fands.Context
We ran into this in NServiceBus, where saga mappings like
m => ((Start)m).Idon an interface-typed message are compiled withCompileFast(). From the outside it looks like the unbox is missing when the source type is an interface, since theobjectcase works, but I haven't looked at the emitted IL to confirm that.Thanks for maintaining FastExpressionCompiler!