Reputation: 47904
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>'
()
Upvotes: 5
Views: 240
Reputation: 55185
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).
Upvotes: 3
Reputation: 78272
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.
Upvotes: 1