Proposal: A clear and standard syntax for instrumentation #85
Replies: 20 comments
-
I have some qualms about C# supporting both I'd be fine with the use of an interface that offers all of the supported members, but I would also prefer the compiler support instrumentation by convention. That way as long as the instrumentation value exposes the correct accessible members (or they can be resolved via extension methods), the code would continue to compile. I would also allow for partial implementations of the convention. One thing that seems missing is a way to define a back-off algorithm for retries. At best I could see the instrumentation value exposing a public property with the attempt and to have the action manually implement the back-off strategy, but I could see that being built into the instrumentation value. |
Beta Was this translation helpful? Give feedback.
-
I think |
Beta Was this translation helpful? Give feedback.
-
I'm fine with either approach, adding a I don't care of the As for using @jnm2's point about accidentally thinking an |
Beta Was this translation helpful? Give feedback.
-
Perhaps Currently, It looks viable to support a new syntax, |
Beta Was this translation helpful? Give feedback.
-
Yes, it gives the notion that it can be executed multiple times, but it also gives the implication that it's a loop, which it isn't. |
Beta Was this translation helpful? Give feedback.
-
As context for a better keyword, consider the following cursor change class: public class WaitCursor : IDisposable
{
public WaitCursor()
{
IsWaitCursor = true;
}
public void Dispose()
{
IsWaitCursor = false;
}
public bool IsWaitCursor
{
get
{
return Application.UseWaitCursor;
}
set
{
if (Application.UseWaitCursor != value)
{
Application.UseWaitCursor = value;
Cursor.Current = value ? Cursors.WaitCursor : Cursors.Default;
}
}
}
} The proposed choices for syntax are: // Current working syntax
using (var waitCursor = new WaitCursor())
{
}
// Proposed new syntax options
with (var waitCursor = new WaitCursor())
{
}
scoped (var waitCursor = new WaitCursor())
{
}
for (var waitCursor = new WaitCursor())
{
}
instrument (var waitCursor = new WaitCursor())
{
} |
Beta Was this translation helpful? Give feedback.
-
My preference is |
Beta Was this translation helpful? Give feedback.
-
I think you are being over ideological. To a programmer, |
Beta Was this translation helpful? Give feedback.
-
Yes, that's useful but only in a narrowly defined use case, specifically that of instrumenting the code. A general purpose "scoped unit of work" block wouldn't provide any explicit mechanism to capture exceptions, handle retries, etc. It's much more equivalent to a @HaloFour I'm good with expanding |
Beta Was this translation helpful? Give feedback.
-
@HaloFour The other difference is that I can currently write code like this: using (new WaitCursor())
{
} If we introduced a new keyword, it could require that using (var wc = new WaitCursor())
{
if (!wc.Success)
{
// handle the failure condition
}
} |
Beta Was this translation helpful? Give feedback.
-
This is what If you want to slightly improve |
Beta Was this translation helpful? Give feedback.
-
Correct, which is why I think having a different mechanism to do this would, ultimately, be better. That way,
This proposal, as it stands, really sounds like wanting language support for the old patterns and practices transient fault handling block. I think (it's been a while since I've looked at it) that code handled everything in this proposal, although it used lambdas rather than actual language syntax. |
Beta Was this translation helpful? Give feedback.
-
Just want to suggest an improvement to the compiler-generated code to use an exception filter for handling the exception case (see below). var _temp = object-Expr;
for (var _i = 0; ; ++_i) {
_temp.NotifyAttempt(_i);
try
{
// action code
}
catch (Exception ex) when (!_temp.NotifyException(_i, ex))
{
continue; // Instrument wants a retry.
}
_temp.NotifySuccess(_i);
break;
} |
Beta Was this translation helpful? Give feedback.
-
As long as using the exception filter doesn't introduce more overhead than without it. What does the exception filter expand to? I couldn't find the proposal. |
Beta Was this translation helpful? Give feedback.
-
@ManishJayaswal, this has some overlap with both generators and dynamic analyzers. |
Beta Was this translation helpful? Give feedback.
-
Exception filters are a CLR feature exposed by C#6. There's no expansion. The key thing is it avoids |
Beta Was this translation helpful? Give feedback.
-
@pharring Thanks for that. I had forgotten that they were already a CLR feature just being exposed by language syntax. I think that's a worthwhile syntax change to make and the difference you mentioned about |
Beta Was this translation helpful? Give feedback.
-
I would support |
Beta Was this translation helpful? Give feedback.
-
The using (var foo = new Foo() in IDisposable) { ... } // the "standard"
using (var foo = new Foo() in IInstrumentation) { ... } to be read as "... in IDisposable context". On step further this could allow for custom implementations. internal using interface IMyDisposable {
void Dispose();
void using(IMyDisposable subject) {
try {
using;
}
finally { subject.Dispose(); }
}
} It's a bit much There can be more |
Beta Was this translation helpful? Give feedback.
-
Another one attempt to solve add general instrumentation: #3025 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
[adapted from Roslyn Issue 16896]
Background
It's common in the implementation of logging, retry handlers and other helpers, to have a method that observes and manages execution of a code block. This is called manual or explicit "instrumentation", and in C# is usually performed with lambdas:
Lambdas imply the following:
for
statement, or immediately return a value for the declaring method. They force the developer to return a flag or temporary object to the observer method, which then returns to the caller, which then must be evaluated and processed. That's a lot of boilerplate just to add instrumentation.Some people suggest the
using
keyword as way of avoiding lambda issues, butusing
doesn't notify theIDisposable
object about exceptions. This prevents observation of certain outcomes. Also, theIDisposable
interface is designed for release of unmanaged resources. There is a general assumption that an object cannot be reused after it's disposed, and this assumption doesn't hold for observers. And finally,using
cannot transparently issue a retry attempt.Proposal
The proposal is to make C# support a clear form of instrumentation:
The object-Expr must return an instance of
IInstrument
. The action code is free to return a value for the declaring method, or to interact with parent flow statements (ie, it can havebreak
andcontinue
). This makes instrumentation much cleaner and less invasive and disruptive than a lambda. There are clear benefits in readability and editability, not to mention simpler stacktraces and mildly improved performance.The above code is compiled roughly as if the developer had written:
With this feature, a provider of
IInstrument
implementation can:NotifySuccess
call.The provider can also back off (for instance, sleep) during execution of
NotifyAttempt
. In order to support async code, theIInstrument
interface should also declare the async versionNotifyAttemptAsync
. The compiler decides which method to call from the style of parent method. This brings the additional benefits:with (foo) { ... }
.Beta Was this translation helpful? Give feedback.
All reactions