The use wildcard attaches work in a sequence expression, but not otherwise

Using a lookup pattern with use works in a sequence expression, but not otherwise. Is there a reason for this?

 let mkDisposable() = { new IDisposable with member __.Dispose() = () } let mkSeq() = seq { use _ = mkDisposable() //OK () } let f() = use _ = mkDisposable() //ERROR: 'use' bindings must be of the form 'use <var> = <expr>' () 
+4
source share
2 answers

I believe this is a natural (but unexpected) consequence of desugaring calculations (an expression of the sequence in this case, but this behavior applies to all calculations). As the specification shows ,

 use pat = expr cexpr 

translates to

 Using(expr, fun pat -> cepxr) 

Since this is a fuzzy syntax translation, you can use any template that you can use when writing a function, including only _ . However, for regular use bindings, the left side of the binding should be an identifier, not a template (see section 6.6.3 of the specification ).

+2
source

I did a little work and it seems that special seq expressions are being processed that change the use rules. The seq expression is actually converted to the next with the IDisposable field, which is located after the completion of the sequence.

 internal sealed class mkSeq@11 <a> : GeneratedSequenceBase<a> { [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode] public IDisposable matchValue = matchValue; [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode] public int pc = pc; [DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode] public a current = current; public mkSeq@11 (IDisposable matchValue, int pc, a current) { } public override int GenerateNext(ref IEnumerable<a> next) { switch (this.pc) { case 2: { break; } case 3: { goto IL_55; } default: { this.matchValue = Program.mkDisposable(); this.pc = 2; break; } } this.pc = 3; LanguagePrimitives.IntrinsicFunctions.Dispose<IDisposable>(this.matchValue); this.matchValue = null; this.pc = 3; IL_55: this.current = default(a); return 0; } public override void Close() { switch (this.pc) { case 1: { goto IL_41; } case 3: { goto IL_41; } } this.pc = 3; LanguagePrimitives.IntrinsicFunctions.Dispose<IDisposable>(this.matchValue); IL_41: this.pc = 3; this.current = default(a); } public override bool get_CheckClose() { switch (this.pc) { case 1: { return false; } case 3: { return false; } } return true; } [CompilerGenerated, DebuggerNonUserCode] public override a get_LastGenerated() { return this.current; } [CompilerGenerated, DebuggerNonUserCode] public override IEnumerator<a> GetFreshEnumerator() { return new Program<a> .mkSeq@11 (null, 0, default(a)); } } 

Usually use translates to this:

 IDisposable e = Program.mkDisposable(); try { } finally { IDisposable disposable = e as IDisposable; if (disposable != null) { disposable.Dispose(); } } 

Without a variable name, the compiler will ignore the result of the expression and therefore cannot be deleted. Honestly, there must be a special case for use , so the entire template template is created behind the scenes, as we see in the seq expressions.

+1
source

Source: https://habr.com/ru/post/1385437/


All Articles