diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index 618f0d5af..669c9c4e4 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -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
public virtual void VisitUsingStatement(UsingStatement usingStatement)
{
StartNode(usingStatement);
+ if (usingStatement.IsAsync) {
+ WriteKeyword(UsingStatement.AwaitRole);
+ }
WriteKeyword(UsingStatement.UsingKeywordRole);
Space(policy.SpaceBeforeUsingParentheses);
LPar();
diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
index 06ea73131..735c97f5a 100644
--- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
@@ -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
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
}
return new UsingStatement {
ResourceAcquisition = usingInit,
+ IsAsync = inst.IsAsync,
EmbeddedStatement = ConvertAsBlock(inst.Body)
};
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs
index 814819e8d..b8ad576a8 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/UsingStatement.cs
@@ -28,57 +28,67 @@
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
///
- /// using (ResourceAcquisition) EmbeddedStatement
+ /// [ await ] using (ResourceAcquisition) EmbeddedStatement
///
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 ResourceAcquisitionRole = new Role("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); }
}
-
+
///
/// Either a VariableDeclarationStatement, or an Expression.
///
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 (IAstVisitor visitor)
+
+ public override T AcceptVisitor(IAstVisitor visitor)
{
- return visitor.VisitUsingStatement (this);
+ return visitor.VisitUsingStatement(this);
}
-
- public override S AcceptVisitor (IAstVisitor visitor, T data)
+
+ public override S AcceptVisitor(IAstVisitor 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);
}
}
}
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index febdbd6c1..fed45dec5 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -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
}
}
+ 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;
///
diff --git a/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
index 61913e619..f44d30459 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
@@ -36,10 +36,16 @@ namespace ICSharpCode.Decompiler.IL
///
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);
diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
index 86e0cecf2..31c8c91d5 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
@@ -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
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
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
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
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
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
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
}
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
|| (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));
}
}
+
+ ///
+ /// 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)
+ /// }
+ ///
+ /// }
+ ///
+ 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;
+ }
}
}
diff --git a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
index e9c100d31..805f95b14 100644
--- a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
@@ -115,10 +115,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
Task,
/// System.Threading.Tasks.Task{T}
TaskOfT,
+ /// System.Threading.Tasks.ValueTask
+ ValueTask,
+ /// System.Threading.Tasks.ValueTask{T}
+ ValueTaskOfT,
/// System.Nullable{T}
NullableOfT,
/// System.IDisposable
IDisposable,
+ /// System.IAsyncDisposable
+ IAsyncDisposable,
/// System.Runtime.CompilerServices.INotifyCompletion
INotifyCompletion,
/// System.Runtime.CompilerServices.ICriticalNotifyCompletion
@@ -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"),
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index d51ba0b1f..dd8e2a829 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -673,6 +673,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Detect awaited using and foreach statements.
+ ///
+ public static string DecompilerSettings_DetectAsyncUsingAndForeachStatements {
+ get {
+ return ResourceManager.GetString("DecompilerSettings.DetectAsyncUsingAndForeachStatements", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Detect foreach statements.
///
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index dbe93bffd..7aa7db1df 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -447,9 +447,6 @@
Version
-
- Culture
-
Public Key Token
@@ -775,4 +772,7 @@ Are you sure you want to continue?
Read-only methods
+
+ Detect awaited using and foreach statements
+
\ No newline at end of file