Browse Source

Add transform to remove unconstrained generic reference type check.

pull/3440/head
Siegfried Pammer 4 months ago
parent
commit
6c72d1c5f0
  1. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 48
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
  3. 2
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  4. 8
      ICSharpCode.Decompiler/DecompilerSettings.cs
  5. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  6. 2
      ICSharpCode.Decompiler/IL/ILReader.cs
  7. 12
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  8. 10
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  9. 1
      ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
  10. 45
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  11. 145
      ICSharpCode.Decompiler/IL/Transforms/RemoveUnconstrainedGenericReferenceTypeCheck.cs
  12. 1
      ILSpy/Languages/ILAstLanguage.cs

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -17,7 +17,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn> <NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn>
<DefineConstants>ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants> <DefineConstants>ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute> <GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute> <GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute>

48
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs

@ -84,6 +84,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
void Method1<T>() where T : class; void Method1<T>() where T : class;
void Method2<T>() where T : class; void Method2<T>() where T : class;
void Method3(int a, string b, Type c);
#if CS72
void Method4(in int a);
#endif
} }
public abstract class Base : IInterface public abstract class Base : IInterface
@ -95,6 +100,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
void IInterface.Method2<T>() void IInterface.Method2<T>()
{ {
} }
void IInterface.Method3(int a, string b, Type c)
{
}
#if CS72
void IInterface.Method4(in int a)
{
}
#endif
} }
public class Derived : Base public class Derived : Base
@ -302,5 +317,38 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
x.Dispose(); x.Dispose();
y.Dispose(); y.Dispose();
} }
// prior to C# 7.0 UseRefLocalsForAccurateOrderOfEvaluation is disabled, so we will inline.
// Roslyn 4 generates the explicit ldobj.if.ref pattern, so we can also inline.
// The versions in between, we don't inline, so the code doesn't look pretty.
#if ROSLYN4 || !CS70
public static int[] Issue3438<T>(T[] array)
{
List<int> list = new List<int>();
for (int i = 0; i < array.Length; i++)
{
if (!array[i].Equals(default(T)))
{
list.Add(i);
}
}
return list.ToArray();
}
public void Issue3438b<T>(T[] item1, T item2, int item3) where T : IInterface
{
item1[CastToInt(item2)].Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
}
public void Issue3438c<T>(T item, T item2, int item3) where T : IInterface
{
CastFromInt<T>(item3).Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
}
//#if CS72
// Disabled because ILInlining does not support inlining ldloca currently.
// public void Issue3438d<T>(T[] item1, T item2, int item3) where T : IInterface
// {
// item1[CastToInt(item2)].Method4(CastToInt(item2));
// }
//#endif
#endif
} }
} }

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -149,6 +149,7 @@ namespace ICSharpCode.Decompiler.CSharp
new IndexRangeTransform(), new IndexRangeTransform(),
new DeconstructionTransform(), new DeconstructionTransform(),
new NamedArgumentTransform(), new NamedArgumentTransform(),
new RemoveUnconstrainedGenericReferenceTypeCheck(),
new UserDefinedLogicTransform(), new UserDefinedLogicTransform(),
new InterpolatedStringTransform() new InterpolatedStringTransform()
), ),
@ -1717,6 +1718,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
var ilReader = new ILReader(typeSystem.MainModule) { var ilReader = new ILReader(typeSystem.MainModule) {
UseDebugSymbols = settings.UseDebugSymbols, UseDebugSymbols = settings.UseDebugSymbols,
UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfoProvider DebugInfo = DebugInfoProvider
}; };
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken); var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);

8
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -92,7 +92,6 @@ namespace ICSharpCode.Decompiler
stringInterpolation = false; stringInterpolation = false;
dictionaryInitializers = false; dictionaryInitializers = false;
extensionMethodsInCollectionInitializers = false; extensionMethodsInCollectionInitializers = false;
useRefLocalsForAccurateOrderOfEvaluation = false;
getterOnlyAutomaticProperties = false; getterOnlyAutomaticProperties = false;
} }
if (languageVersion < CSharp.LanguageVersion.CSharp7) if (languageVersion < CSharp.LanguageVersion.CSharp7)
@ -105,6 +104,7 @@ namespace ICSharpCode.Decompiler
localFunctions = false; localFunctions = false;
deconstruction = false; deconstruction = false;
patternMatching = false; patternMatching = false;
useRefLocalsForAccurateOrderOfEvaluation = false;
} }
if (languageVersion < CSharp.LanguageVersion.CSharp7_2) if (languageVersion < CSharp.LanguageVersion.CSharp7_2)
{ {
@ -190,11 +190,11 @@ namespace ICSharpCode.Decompiler
return CSharp.LanguageVersion.CSharp7_2; return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing // C# 7.1 missing
if (outVariables || throwExpressions || tupleTypes || tupleConversions if (outVariables || throwExpressions || tupleTypes || tupleConversions
|| discards || localFunctions || deconstruction || patternMatching) || discards || localFunctions || deconstruction || patternMatching || useRefLocalsForAccurateOrderOfEvaluation)
return CSharp.LanguageVersion.CSharp7; return CSharp.LanguageVersion.CSharp7;
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers || stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers
|| useRefLocalsForAccurateOrderOfEvaluation || getterOnlyAutomaticProperties) || getterOnlyAutomaticProperties)
return CSharp.LanguageVersion.CSharp6; return CSharp.LanguageVersion.CSharp6;
if (asyncAwait) if (asyncAwait)
return CSharp.LanguageVersion.CSharp5; return CSharp.LanguageVersion.CSharp5;
@ -1126,7 +1126,7 @@ namespace ICSharpCode.Decompiler
/// order of evaluation. /// order of evaluation.
/// See https://github.com/icsharpcode/ILSpy/issues/2050 /// See https://github.com/icsharpcode/ILSpy/issues/2050
/// </summary> /// </summary>
[Category("C# 6.0 / VS 2015")] [Category("C# 7.0 / VS 2017")]
[Description("DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation")] [Description("DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation")]
public bool UseRefLocalsForAccurateOrderOfEvaluation { public bool UseRefLocalsForAccurateOrderOfEvaluation {
get { return useRefLocalsForAccurateOrderOfEvaluation; } get { return useRefLocalsForAccurateOrderOfEvaluation; }

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -107,6 +107,7 @@
<Compile Include="DecompilationProgress.cs" /> <Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" /> <Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" /> <Compile Include="Disassembler\SortByNameProcessor.cs" />
<Compile Include="IL\Transforms\RemoveUnconstrainedGenericReferenceTypeCheck.cs" />
<Compile Include="Metadata\MetadataFile.cs" /> <Compile Include="Metadata\MetadataFile.cs" />
<Compile Include="Metadata\ModuleReferenceMetadata.cs" /> <Compile Include="Metadata\ModuleReferenceMetadata.cs" />
<Compile Include="NRTAttributes.cs" /> <Compile Include="NRTAttributes.cs" />

2
ICSharpCode.Decompiler/IL/ILReader.cs

@ -122,6 +122,7 @@ namespace ICSharpCode.Decompiler.IL
readonly MetadataReader metadata; readonly MetadataReader metadata;
public bool UseDebugSymbols { get; set; } public bool UseDebugSymbols { get; set; }
public bool UseRefLocalsForAccurateOrderOfEvaluation { get; set; }
public DebugInfo.IDebugInfoProvider? DebugInfo { get; set; } public DebugInfo.IDebugInfoProvider? DebugInfo { get; set; }
public List<string> Warnings { get; } = new List<string>(); public List<string> Warnings { get; } = new List<string>();
@ -1770,6 +1771,7 @@ namespace ICSharpCode.Decompiler.IL
StackType expectedStackType = CallInstruction.ExpectedTypeForThisPointer(method.DeclaringType, constrainedPrefix); StackType expectedStackType = CallInstruction.ExpectedTypeForThisPointer(method.DeclaringType, constrainedPrefix);
bool requiresLdObjIfRef = firstArgument == 1 bool requiresLdObjIfRef = firstArgument == 1
&& !firstArgumentIsStObjTarget && !firstArgumentIsStObjTarget
&& UseRefLocalsForAccurateOrderOfEvaluation
&& expectedStackType == StackType.Ref && typeOfThis.IsReferenceType != false; && expectedStackType == StackType.Ref && typeOfThis.IsReferenceType != false;
for (int i = method.Parameters.Count - 1; i >= 0; i--) for (int i = method.Parameters.Count - 1; i >= 0; i--)
{ {

12
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -581,5 +581,17 @@ namespace ICSharpCode.Decompiler.IL
{ {
return this; return this;
} }
public bool MatchLdObj([NotNullWhen(true)] out ILInstruction? target, IType type)
{
var inst = this as LdObj;
if (inst != null && inst.Type.Equals(type))
{
target = inst.Target;
return true;
}
target = default(ILInstruction);
return false;
}
} }
} }

10
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -485,6 +485,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst.ReplaceWith(inst.Target); inst.ReplaceWith(inst.Target);
return; return;
} }
if (inst.Target.MatchLdLoc(out var s) && s.IsSingleDefinition && s.LoadCount == 1
&& s.StoreInstructions.SingleOrDefault() is StLoc {
Value: LdLoca { Variable: { AddressCount: 1, StoreCount: 1 } }
})
{
context.Step("Single use of ldobj.if.ref(ldloc v) -> ldloc v", inst);
// there already is a temporary, so the ldobj.if.ref is a no-op in both cases
inst.ReplaceWith(inst.Target);
return;
}
} }
protected internal override void VisitStObj(StObj inst) protected internal override void VisitStObj(StObj inst)

1
ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs

@ -79,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
return new ILReader(TypeSystem.MainModule) { return new ILReader(TypeSystem.MainModule) {
UseDebugSymbols = Settings.UseDebugSymbols, UseDebugSymbols = Settings.UseDebugSymbols,
UseRefLocalsForAccurateOrderOfEvaluation = Settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfo DebugInfo = DebugInfo
}; };
} }

45
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -170,14 +170,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public static bool InlineOneIfPossible(Block block, int pos, InliningOptions options, ILTransformContext context) public static bool InlineOneIfPossible(Block block, int pos, InliningOptions options, ILTransformContext context)
{ {
context.CancellationToken.ThrowIfCancellationRequested(); context.CancellationToken.ThrowIfCancellationRequested();
StLoc stloc = block.Instructions[pos] as StLoc; if (block.Instructions[pos] is not StLoc stloc)
if (stloc == null || stloc.Variable.Kind == VariableKind.PinnedLocal)
return false; return false;
ILVariable v = stloc.Variable; if (!VariableCanBeUsedForInlining(stloc.Variable))
// ensure the variable is accessed only a single time
if (v.StoreCount != 1)
return false;
if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1)
return false; return false;
// TODO: inlining of small integer types might be semantically incorrect, // TODO: inlining of small integer types might be semantically incorrect,
// but we can't avoid it this easily without breaking lots of tests. // but we can't avoid it this easily without breaking lots of tests.
@ -186,6 +181,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return InlineOne(stloc, options, context); return InlineOne(stloc, options, context);
} }
public static bool VariableCanBeUsedForInlining(ILVariable v)
{
if (v.Kind == VariableKind.PinnedLocal)
return false;
// ensure the variable is accessed only a single time
if (v.StoreCount != 1)
return false;
if (v.LoadCount + v.AddressCount != 1)
return false;
return true;
}
/// <summary> /// <summary>
/// Inlines the stloc instruction at block.Instructions[pos] into the next instruction. /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction.
/// ///
@ -908,6 +915,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
/// <summary>
/// Gets whether 'expressionBeingMoved' can be moved from somewhere before 'stmt' to become the replacement of 'targetLoad'.
/// </summary>
public static bool CanMoveIntoCallVirt(ILInstruction expressionBeingMoved, ILInstruction stmt, CallVirt nestedCallVirt, ILInstruction targetLoad)
{
Debug.Assert(targetLoad.IsDescendantOf(stmt) && nestedCallVirt.IsDescendantOf(stmt));
ILInstruction thisArg = nestedCallVirt.Arguments[0];
Debug.Assert(thisArg is LdObjIfRef);
for (ILInstruction inst = targetLoad; inst != stmt; inst = inst.Parent)
{
if (!inst.Parent.CanInlineIntoSlot(inst.ChildIndex, expressionBeingMoved))
return false;
// Check whether re-ordering with predecessors is valid:
int childIndex = inst.ChildIndex;
for (int i = 0; i < childIndex; ++i)
{
ILInstruction predecessor = inst.Parent.Children[i];
if (predecessor != thisArg && !IsSafeForInlineOver(predecessor, expressionBeingMoved))
return false;
}
}
return true;
}
/// <summary> /// <summary>
/// Gets whether arg can be un-inlined out of stmt. /// Gets whether arg can be un-inlined out of stmt.
/// </summary> /// </summary>

145
ICSharpCode.Decompiler/IL/Transforms/RemoveUnconstrainedGenericReferenceTypeCheck.cs

@ -0,0 +1,145 @@
// Copyright (c) 2025 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Collections.Generic;
using System.Linq;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// [stloc address(...)]
/// stloc temp(default.value ``0)
/// if (comp.o(ldloc temp == ldnull)) Block IL_002a {
/// stloc temp(ldobj ``0(ldloc address))
/// stloc address(ldloca temp)
/// }
/// stloc V_i(expr_i)
/// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
///
///=>
///
/// [stloc address(...)]
/// stloc address(ldobj.if.ref(ldloc address))
/// stloc V_i(expr_i)
/// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
///
/// Then ldobj.if.ref in the call is redundant because any object reference was already loaded into an immutable temporary.
/// So we can removed and inlining of the arguments (V_i) becomes possible.
///
/// Finally the newly created ldobj.if.ref is inlined into the place where the old ldobj.if.ref was.
///
/// =>
///
/// [stloc address(...)]
/// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), expr_i ...))...
///
/// </summary>
class RemoveUnconstrainedGenericReferenceTypeCheck : IStatementTransform
{
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{
int startPos = pos;
// stloc temp(default.value ``0)
if (!block.Instructions[pos].MatchStLoc(out var temp, out var defaultValue))
return;
if (!defaultValue.MatchDefaultValue(out var type))
return;
if (temp.StoreCount != 2 || temp.LoadCount != 1 || temp.AddressCount != 1)
return;
pos++;
// if (comp.o(ldloc temp == ldnull)) Block IL_002a {
// stloc temp(ldobj ``0(ldloc address))
// stloc address(ldloca temp)
// }
if (block.Instructions.ElementAtOrDefault(pos) is not IfInstruction ifInst)
return;
if (!ifInst.Condition.MatchCompEqualsNull(out var tempLoadForNullCheck))
return;
if (!tempLoadForNullCheck.MatchLdLoc(temp))
return;
if (ifInst.TrueInst is not Block dereferenceBlock)
return;
if (ifInst.FalseInst is not Nop)
return;
if (dereferenceBlock.Instructions is not [StLoc tempStore, StLoc addressReassign])
return;
if (tempStore.Variable != temp)
return;
if (!tempStore.Value.MatchLdObj(out var addressLoadForLdObj, type))
return;
if (!addressLoadForLdObj.MatchLdLoc(addressReassign.Variable))
return;
if (!addressReassign.Value.MatchLdLoca(temp))
return;
var address = addressReassign.Variable;
if (address.StoreCount != 2 || address.LoadCount != 2 || address.AddressCount != 0)
return;
pos++;
// pos now is the first store to V_i
// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
var callTarget = address.LoadInstructions.Single(l => addressLoadForLdObj != l);
if (callTarget.Parent is not LdObjIfRef { Parent: CallVirt call } ldobjIfRef)
return;
if (call.Arguments.Count == 0 || call.Arguments[0] != ldobjIfRef || !type.Equals(call.ConstrainedTo))
return;
ILInstruction containingStmt = call;
while (containingStmt.Parent != block)
{
if (containingStmt.Parent == null)
return;
containingStmt = containingStmt.Parent;
}
if (containingStmt.ChildIndex < pos)
return;
// check if we can inline all temporaries used in the call:
int temporaryInitIndex = containingStmt.ChildIndex - 1;
List<(ILInstruction, ILInstruction)> replacements = new();
for (int argIndex = call.Arguments.Count - 1; argIndex > 0 && temporaryInitIndex >= pos; argIndex--)
{
var argument = call.Arguments[argIndex];
switch (argument)
{
case LdLoc load:
if (block.Instructions[temporaryInitIndex].MatchStLoc(load.Variable, out var expr) && ILInlining.VariableCanBeUsedForInlining(load.Variable))
{
if (!ILInlining.CanMoveIntoCallVirt(expr, containingStmt, call, argument))
{
return;
}
replacements.Add((argument, expr));
temporaryInitIndex--;
}
break;
}
}
// all stores to V_i processed?
if (temporaryInitIndex != pos - 1)
{
return;
}
context.Step("RemoveUnconstrainedGenericReferenceTypeCheck", block.Instructions[startPos]);
foreach (var (argument, expr) in replacements)
{
argument.ReplaceWith(expr);
}
block.Instructions.RemoveRange(startPos, containingStmt.ChildIndex - startPos);
}
}
}

1
ILSpy/Languages/ILAstLanguage.cs

@ -109,6 +109,7 @@ namespace ICSharpCode.ILSpy
var typeSystem = new DecompilerTypeSystem(module, assemblyResolver); var typeSystem = new DecompilerTypeSystem(module, assemblyResolver);
var reader = new ILReader(typeSystem.MainModule); var reader = new ILReader(typeSystem.MainModule);
reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols; reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols;
reader.UseRefLocalsForAccurateOrderOfEvaluation = options.DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation;
var methodBody = module.GetMethodBody(methodDef.RelativeVirtualAddress); var methodBody = module.GetMethodBody(methodDef.RelativeVirtualAddress);
ILFunction il = reader.ReadIL((SRM.MethodDefinitionHandle)method.MetadataToken, methodBody, kind: ILFunctionKind.TopLevelFunction, cancellationToken: options.CancellationToken); ILFunction il = reader.ReadIL((SRM.MethodDefinitionHandle)method.MetadataToken, methodBody, kind: ILFunctionKind.TopLevelFunction, cancellationToken: options.CancellationToken);
var decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings) { CancellationToken = options.CancellationToken }; var decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings) { CancellationToken = options.CancellationToken };

Loading…
Cancel
Save