diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 82f2044c5..40278bb46 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -201,38 +201,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Inlining a value type variable is allowed only if the resulting code will maintain the semantics
// that the method is operating on a copy.
// Thus, we have to disallow inlining of other locals, fields, array elements, dereferenced pointers
- switch (inlinedExpression.OpCode) {
- case OpCode.LdLoc:
- case OpCode.StLoc:
- return false;
- case OpCode.LdObj:
- // allow inlining field access only if it's a readonly field
- IField f = (((LdObj)inlinedExpression).Target as IInstructionWithFieldOperand)?.Field;
- if (f != null && f.IsReadOnly)
- break;
- return f != null && f.IsReadOnly;
- case OpCode.Call:
- var m = ((CallInstruction)inlinedExpression).Method;
- // ensure that it's not an multi-dimensional array getter
- if (m.DeclaringType.Kind == TypeKind.Array)
- return false;
- goto case OpCode.CallVirt;
- case OpCode.CallVirt:
- // don't inline foreach loop variables:
- m = ((CallInstruction)inlinedExpression).Method;
- if (m.Name == "get_Current" && !m.IsStatic)
- return false;
- break;
- case OpCode.CastClass:
- case OpCode.UnboxAny:
- // These are valid, but might occur as part of a foreach loop variable.
- ILInstruction arg = inlinedExpression.Children[0];
- if (arg.OpCode == OpCode.Call || arg.OpCode == OpCode.CallVirt) {
- m = ((CallInstruction)arg).Method;
- if (m.Name == "get_Current" && !m.IsStatic)
- return false; // looks like a foreach loop variable, so don't inline it
- }
- break;
+ if (IsLValue(inlinedExpression)) {
+ return false;
}
// inline the compiler-generated variable that are used when accessing a member on a value type:
@@ -249,6 +219,37 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
+ ///
+ /// Gets whether the instruction, when converted into C#, turns into an l-value that can
+ /// be used to mutate a value-type.
+ /// If this function returns false, the C# compiler would introduce a temporary copy
+ /// when calling a method on a value-type (and any mutations performed by the method will be lost)
+ ///
+ static bool IsLValue(ILInstruction inst)
+ {
+ switch (inst.OpCode) {
+ case OpCode.LdLoc:
+ case OpCode.StLoc:
+ return true;
+ case OpCode.LdObj:
+ // ldobj typically refers to a storage location,
+ // but readonly fields are an exception.
+ IField f = (((LdObj)inst).Target as IInstructionWithFieldOperand)?.Field;
+ return !(f != null && f.IsReadOnly);
+ case OpCode.StObj:
+ // stobj is the same as ldobj.
+ f = (((StObj)inst).Target as IInstructionWithFieldOperand)?.Field;
+ return !(f != null && f.IsReadOnly);
+ case OpCode.Call:
+ var m = ((CallInstruction)inst).Method;
+ // multi-dimensional array getters are lvalues,
+ // everything else is an rvalue.
+ return m.DeclaringType.Kind == TypeKind.Array;
+ default:
+ return false; // most instructions result in an rvalue
+ }
+ }
+
///
/// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
///