Browse Source

Add support for await using statements.

pull/1730/head
Siegfried Pammer 6 years ago
parent
commit
c7f98a4db7
  1. 4
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  2. 22
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  3. 60
      ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs
  4. 17
      ICSharpCode.Decompiler/DecompilerSettings.cs
  5. 8
      ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
  6. 107
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
  7. 13
      ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
  8. 9
      ILSpy/Properties/Resources.Designer.cs
  9. 6
      ILSpy/Properties/Resources.resx

4
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1112,6 +1112,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1112,6 +1112,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
var opSymbol = UnaryOperatorExpression.GetOperatorRole(opType);
if (opType == UnaryOperatorType.Await) {
WriteKeyword(opSymbol);
Space();
} else if (!IsPostfixOperator(opType) && opSymbol != null) {
WriteToken(opSymbol);
}
@ -1863,6 +1864,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1863,6 +1864,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitUsingStatement(UsingStatement usingStatement)
{
StartNode(usingStatement);
if (usingStatement.IsAsync) {
WriteKeyword(UsingStatement.AwaitRole);
}
WriteKeyword(UsingStatement.UsingKeywordRole);
Space(policy.SpaceBeforeUsingParentheses);
LPar();

22
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -411,14 +411,29 @@ namespace ICSharpCode.Decompiler.CSharp @@ -411,14 +411,29 @@ namespace ICSharpCode.Decompiler.CSharp
return transformed;
AstNode usingInit = resource;
var var = inst.Variable;
if (!inst.ResourceExpression.MatchLdNull() && !NullableType.GetUnderlyingType(var.Type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IDisposable))) {
KnownTypeCode knownTypeCode;
IType disposeType;
string disposeTypeMethodName;
if (inst.IsAsync) {
knownTypeCode = KnownTypeCode.IAsyncDisposable;
disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IAsyncDisposable);
disposeTypeMethodName = "DisposeAsync";
} else {
knownTypeCode = KnownTypeCode.IDisposable;
disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable);
disposeTypeMethodName = "Dispose";
}
if (!inst.ResourceExpression.MatchLdNull() && !NullableType.GetUnderlyingType(var.Type).GetAllBaseTypes().Any(b => b.IsKnownType(knownTypeCode))) {
Debug.Assert(var.Kind == VariableKind.UsingLocal);
var.Kind = VariableKind.Local;
var disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable);
var disposeVariable = currentFunction.RegisterVariable(
VariableKind.Local, disposeType,
AssignVariableNames.GenerateVariableName(currentFunction, disposeType)
);
Expression disposeInvocation = new InvocationExpression(new MemberReferenceExpression(exprBuilder.ConvertVariable(disposeVariable).Expression, disposeTypeMethodName));
if (inst.IsAsync) {
disposeInvocation = new UnaryOperatorExpression { Expression = disposeInvocation, Operator = UnaryOperatorType.Await };
}
return new BlockStatement {
new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(var).Expression, resource.Detach())),
new TryCatchStatement {
@ -427,7 +442,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -427,7 +442,7 @@ namespace ICSharpCode.Decompiler.CSharp
new ExpressionStatement(new AssignmentExpression(exprBuilder.ConvertVariable(disposeVariable).Expression, new AsExpression(exprBuilder.ConvertVariable(var).Expression, exprBuilder.ConvertType(disposeType)))),
new IfElseStatement {
Condition = new BinaryOperatorExpression(exprBuilder.ConvertVariable(disposeVariable), BinaryOperatorType.InEquality, new NullReferenceExpression()),
TrueStatement = new ExpressionStatement(new InvocationExpression(new MemberReferenceExpression(exprBuilder.ConvertVariable(disposeVariable).Expression, "Dispose")))
TrueStatement = new ExpressionStatement(disposeInvocation)
}
}
},
@ -441,6 +456,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -441,6 +456,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
return new UsingStatement {
ResourceAcquisition = usingInit,
IsAsync = inst.IsAsync,
EmbeddedStatement = ConvertAsBlock(inst.Body)
};
}

60
ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs

@ -28,57 +28,67 @@ @@ -28,57 +28,67 @@
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
/// <summary>
/// using (ResourceAcquisition) EmbeddedStatement
/// [ await ] using (ResourceAcquisition) EmbeddedStatement
/// </summary>
public class UsingStatement : Statement
{
public static readonly TokenRole UsingKeywordRole = new TokenRole ("using");
public static readonly TokenRole UsingKeywordRole = new TokenRole("using");
public static readonly TokenRole AwaitRole = UnaryOperatorExpression.AwaitRole;
public static readonly Role<AstNode> ResourceAcquisitionRole = new Role<AstNode>("ResourceAcquisition", AstNode.Null);
public CSharpTokenNode UsingToken {
get { return GetChildByRole (UsingKeywordRole); }
get { return GetChildByRole(UsingKeywordRole); }
}
public CSharpTokenNode AwaitToken {
get { return GetChildByRole(AwaitRole); }
}
public bool IsAsync {
get { return !GetChildByRole(AwaitRole).IsNull; }
set { SetChildByRole(AwaitRole, value ? new CSharpTokenNode(TextLocation.Empty, null) : null); }
}
public CSharpTokenNode LParToken {
get { return GetChildByRole (Roles.LPar); }
get { return GetChildByRole(Roles.LPar); }
}
/// <summary>
/// Either a VariableDeclarationStatement, or an Expression.
/// </summary>
public AstNode ResourceAcquisition {
get { return GetChildByRole (ResourceAcquisitionRole); }
set { SetChildByRole (ResourceAcquisitionRole, value); }
get { return GetChildByRole(ResourceAcquisitionRole); }
set { SetChildByRole(ResourceAcquisitionRole, value); }
}
public CSharpTokenNode RParToken {
get { return GetChildByRole (Roles.RPar); }
get { return GetChildByRole(Roles.RPar); }
}
public Statement EmbeddedStatement {
get { return GetChildByRole (Roles.EmbeddedStatement); }
set { SetChildByRole (Roles.EmbeddedStatement, value); }
get { return GetChildByRole(Roles.EmbeddedStatement); }
set { SetChildByRole(Roles.EmbeddedStatement, value); }
}
public override void AcceptVisitor (IAstVisitor visitor)
public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitUsingStatement (this);
visitor.VisitUsingStatement(this);
}
public override T AcceptVisitor<T> (IAstVisitor<T> visitor)
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{
return visitor.VisitUsingStatement (this);
return visitor.VisitUsingStatement(this);
}
public override S AcceptVisitor<T, S> (IAstVisitor<T, S> visitor, T data)
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitUsingStatement (this, data);
return visitor.VisitUsingStatement(this, data);
}
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
UsingStatement o = other as UsingStatement;
return o != null && this.ResourceAcquisition.DoMatch(o.ResourceAcquisition, match) && this.EmbeddedStatement.DoMatch(o.EmbeddedStatement, match);
return o != null && this.IsAsync == o.IsAsync && this.ResourceAcquisition.DoMatch(o.ResourceAcquisition, match) && this.EmbeddedStatement.DoMatch(o.EmbeddedStatement, match);
}
}
}

17
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -108,13 +108,14 @@ namespace ICSharpCode.Decompiler @@ -108,13 +108,14 @@ namespace ICSharpCode.Decompiler
if (languageVersion < CSharp.LanguageVersion.CSharp8_0) {
nullableReferenceTypes = false;
readOnlyMethods = false;
asyncUsingAndForEachStatement = false;
asyncEnumerator = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator)
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement)
return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers)
return CSharp.LanguageVersion.CSharp7_3;
@ -876,6 +877,20 @@ namespace ICSharpCode.Decompiler @@ -876,6 +877,20 @@ namespace ICSharpCode.Decompiler
}
}
bool asyncUsingAndForEachStatement = true;
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.DetectAsyncUsingAndForeachStatements")]
public bool AsyncUsingAndForEachStatement {
get { return asyncUsingAndForEachStatement; }
set {
if (asyncUsingAndForEachStatement != value) {
asyncUsingAndForEachStatement = value;
OnPropertyChanged();
}
}
}
bool introduceUnmanagedConstraint = true;
/// <summary>

8
ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs

@ -36,10 +36,16 @@ namespace ICSharpCode.Decompiler.IL @@ -36,10 +36,16 @@ namespace ICSharpCode.Decompiler.IL
/// </remarks>
partial class UsingInstruction
{
public bool IsAsync { get; set; }
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
output.Write("using (");
output.Write("using");
if (IsAsync) {
output.Write(".async");
}
output.Write(" (");
Variable.WriteTo(output);
output.Write(" = ");
ResourceExpression.WriteTo(output, options);

107
ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!context.Settings.UsingStatement) return;
this.context = context;
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (!TransformUsing(block, i) && !TransformUsingVB(block, i))
if (!TransformUsing(block, i) && !TransformUsingVB(block, i) && !TransformAsyncUsing(block, i))
continue;
// This happens in some cases:
// Use correct index after transformation.
@ -167,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -167,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull)
bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, in string disposeMethodFullName = "System.IDisposable.Dispose", KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable)
{
var entryPoint = container.EntryPoint;
if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1)
@ -180,17 +180,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -180,17 +180,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (castIndex > -1) {
if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst))
return false;
if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(KnownTypeCode.IDisposable))
if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(disposeTypeCode))
return false;
if (!tempVar.IsSingleDefinition)
return false;
isReference = true;
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck))
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode))
return false;
if (tempVar.LoadCount != numObjVarLoadsInCheck)
return false;
} else {
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _))
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _, disposeMethodFullName, disposeTypeCode))
return false;
}
if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop())
@ -198,9 +198,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -198,9 +198,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck)
bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck, in string disposeMethodFullName, KnownTypeCode disposeTypeCode)
{
numObjVarLoadsInCheck = 2;
ILInstruction disposeInvocation;
CallVirt callVirt;
if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) {
if (checkInst.MatchIfInstruction(out var condition, out var disposeInst)) {
@ -208,22 +209,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -208,22 +209,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
return false;
callVirt = disposeBlock.Instructions[0] as CallVirt;
disposeInvocation = disposeBlock.Instructions[0];
} else if (checkInst.MatchNullableRewrap(out disposeInst)) {
callVirt = disposeInst as CallVirt;
disposeInvocation = disposeInst;
} else {
return false;
}
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref disposeInvocation))
return false;
}
callVirt = disposeInvocation as CallVirt;
if (callVirt == null)
return false;
if (callVirt.Method.FullName != "System.IDisposable.Dispose")
if (callVirt.Method.FullName != disposeMethodFullName)
return false;
if (callVirt.Method.Parameters.Count > 0)
return false;
if (callVirt.Arguments.Count != 1)
return false;
var firstArg = callVirt.Arguments.FirstOrDefault();
if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(KnownTypeCode.IDisposable))) {
if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(disposeTypeCode))) {
if (!firstArg.MatchAddressOf(out var innerArg2, out _))
return false;
return NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar)
@ -255,7 +261,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -255,7 +261,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
return false;
if (!(disposeBlock.Instructions[0] is CallVirt cv))
disposeInvocation = disposeBlock.Instructions[0];
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref disposeInvocation))
return false;
}
if (!(disposeInvocation is CallVirt cv))
return false;
target = cv.Arguments.FirstOrDefault();
if (target == null)
@ -264,6 +275,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -264,6 +275,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = newTarget;
callVirt = cv;
} else {
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref checkInst))
return false;
}
if (!(checkInst is CallVirt cv))
return false;
target = cv.Arguments.FirstOrDefault();
@ -275,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -275,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
callVirt = cv;
}
if (callVirt.Method.FullName != "System.IDisposable.Dispose")
if (callVirt.Method.FullName != disposeMethodFullName)
return false;
if (callVirt.Method.Parameters.Count > 0)
return false;
@ -286,8 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -286,8 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|| (usingNull && callVirt.Arguments[0].MatchLdNull())
|| (isReference && checkInst is NullableRewrap
&& target.MatchIsInst(out var arg, out var type2)
&& arg.MatchLdLoc(objVar) && type2.IsKnownType(KnownTypeCode.IDisposable));
&& arg.MatchLdLoc(objVar) && type2.IsKnownType(disposeTypeCode));
}
}
/// <summary>
/// stloc test(resourceExpression)
/// .try BlockContainer {
/// Block IL_002b (incoming: 1) {
/// call Use(ldloc test)
/// leave IL_002b (nop)
/// }
///
/// } finally BlockContainer {
/// Block IL_0045 (incoming: 1) {
/// if (comp.o(ldloc test == ldnull)) leave IL_0045 (nop)
/// br IL_00ae
/// }
///
/// Block IL_00ae (incoming: 1) {
/// await(addressof System.Threading.Tasks.ValueTask(callvirt DisposeAsync(ldloc test)))
/// leave IL_0045 (nop)
/// }
///
/// }
/// </summary>
private bool TransformAsyncUsing(Block block, int i)
{
if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) return false;
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst))
return false;
if (!CheckAsyncResourceType(storeInst.Variable.Type))
return false;
if (storeInst.Variable.Kind != VariableKind.Local)
return false;
if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally)))
return false;
if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la))))
return false;
if (storeInst.Variable.StoreInstructions.Count > 1)
return false;
if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, usingNull: false, "System.IAsyncDisposable.DisposeAsync", KnownTypeCode.IAsyncDisposable))
return false;
context.Step("AsyncUsingTransform", tryFinally);
storeInst.Variable.Kind = VariableKind.UsingLocal;
block.Instructions.RemoveAt(i);
block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { IsAsync = true }
.WithILRange(storeInst);
return true;
}
bool CheckAsyncResourceType(IType type)
{
if (NullableType.GetUnderlyingType(type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IAsyncDisposable)))
return true;
return false;
}
bool UnwrapAwait(ref ILInstruction awaitInstruction)
{
if (awaitInstruction == null)
return false;
if (!awaitInstruction.MatchAwait(out var arg))
return false;
if (!arg.MatchAddressOf(out awaitInstruction, out var type))
return false;
if (!type.IsKnownType(KnownTypeCode.ValueTask))
return false;
return true;
}
}
}

13
ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs

@ -115,10 +115,16 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -115,10 +115,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
Task,
/// <summary><c>System.Threading.Tasks.Task{T}</c></summary>
TaskOfT,
/// <summary><c>System.Threading.Tasks.ValueTask</c></summary>
ValueTask,
/// <summary><c>System.Threading.Tasks.ValueTask{T}</c></summary>
ValueTaskOfT,
/// <summary><c>System.Nullable{T}</c></summary>
NullableOfT,
/// <summary><c>System.IDisposable</c></summary>
IDisposable,
/// <summary><c>System.IAsyncDisposable</c></summary>
IAsyncDisposable,
/// <summary><c>System.Runtime.CompilerServices.INotifyCompletion</c></summary>
INotifyCompletion,
/// <summary><c>System.Runtime.CompilerServices.ICriticalNotifyCompletion</c></summary>
@ -193,10 +199,13 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -193,10 +199,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
new KnownTypeReference(KnownTypeCode.IReadOnlyCollectionOfT, TypeKind.Interface, "System.Collections.Generic", "IReadOnlyCollection", 1),
new KnownTypeReference(KnownTypeCode.IReadOnlyListOfT, TypeKind.Interface, "System.Collections.Generic", "IReadOnlyList", 1),
new KnownTypeReference(KnownTypeCode.Task, TypeKind.Class, "System.Threading.Tasks", "Task"),
new KnownTypeReference(KnownTypeCode.TaskOfT, TypeKind.Class, "System.Threading.Tasks", "Task", 1, baseType: KnownTypeCode.Task),
new KnownTypeReference(KnownTypeCode.Task, TypeKind.Class, "System.Threading.Tasks", "Task"),
new KnownTypeReference(KnownTypeCode.TaskOfT, TypeKind.Class, "System.Threading.Tasks", "Task", 1, baseType: KnownTypeCode.Task),
new KnownTypeReference(KnownTypeCode.ValueTask, TypeKind.Struct, "System.Threading.Tasks", "ValueTask"),
new KnownTypeReference(KnownTypeCode.ValueTaskOfT, TypeKind.Struct, "System.Threading.Tasks", "ValueTask", 1),
new KnownTypeReference(KnownTypeCode.NullableOfT, TypeKind.Struct, "System", "Nullable", 1),
new KnownTypeReference(KnownTypeCode.IDisposable, TypeKind.Interface, "System", "IDisposable"),
new KnownTypeReference(KnownTypeCode.IAsyncDisposable, TypeKind.Interface, "System", "IAsyncDisposable"),
new KnownTypeReference(KnownTypeCode.INotifyCompletion, TypeKind.Interface, "System.Runtime.CompilerServices", "INotifyCompletion"),
new KnownTypeReference(KnownTypeCode.ICriticalNotifyCompletion, TypeKind.Interface, "System.Runtime.CompilerServices", "ICriticalNotifyCompletion"),

9
ILSpy/Properties/Resources.Designer.cs generated

@ -673,6 +673,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -673,6 +673,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Detect awaited using and foreach statements.
/// </summary>
public static string DecompilerSettings_DetectAsyncUsingAndForeachStatements {
get {
return ResourceManager.GetString("DecompilerSettings.DetectAsyncUsingAndForeachStatements", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detect foreach statements.
/// </summary>

6
ILSpy/Properties/Resources.resx

@ -447,9 +447,6 @@ @@ -447,9 +447,6 @@
<data name="Version" xml:space="preserve">
<value>Version</value>
</data>
<data name="Culture" xml:space="preserve">
<value>Culture</value>
</data>
<data name="PublicToken" xml:space="preserve">
<value>Public Key Token</value>
</data>
@ -775,4 +772,7 @@ Are you sure you want to continue?</value> @@ -775,4 +772,7 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.ReadOnlyMethods" xml:space="preserve">
<value>Read-only methods</value>
</data>
<data name="DecompilerSettings.DetectAsyncUsingAndForeachStatements" xml:space="preserve">
<value>Detect awaited using and foreach statements</value>
</data>
</root>
Loading…
Cancel
Save