Using the wildcard pattern with use
works within 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>'
()
I believe that this is a natural (but unexpected) consequence of the desugaring of computation expressions (a sequence expression in this case, but this behavior applies to all computation expressions). As the spec indicates,
use pat = expr
cexpr
is translated to
Using(expr, fun pat -> cepxr)
Because this is a shallow syntactic translation, you can use any pattern that could be used when writing a function, including just _
. However, for normal use
bindings, the left hand side of the binding must be an identifier, not a pattern (see section 6.6.3 of the spec).
I've done a bit of digging and it seems that the special way seq
expressions are handled changes the rules of use
. The seq
expression is in fact transformed into the following with an IDisposable
field that is disposed upon 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));
}
}
Normally use
is transformed into 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 thus it cannot be disposed. To be honest it seems like a special case should be made for use
so all the boilerplate is created behind the scenes like we see in seq
expressions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With