Safe Delegate invocation and Short try .. catch .. finally syntax

Topics: C# Language Design
Jun 7, 2014 at 5:44 PM
Often when we have some delegate e.g.
Action<T> _delegate;
Each time we want to call it we write:
if( _delegate != null )
    _delegate( ... );
First of all I propose simpler delegate call:
 _delegate?( ... );
Secondary if I want to call each subscriber in safe context I must to write:
var list    = (Action[])_delegate.GetInvocationList ( );

foreach( var action in list )
{
    try             { action( ); }
    catch ( Exception ex )  { Debug.LogException( ex ); }
}
Which creates garbage when calling GetInvocationList.

So i propose to add Method -> IEnumerable<TDelegate> EnumerateInvocationList<TDelegate>( )
and simpler try systax
foreach( var action in _delegate.EnumerateInvocationList( ) )
    try action( ) catch Debug.LogException( $ex );
and even simpler:
try each _delegate catch Debug.LogException( $ex );
or
try each _delegate catch( ExactException ex ) Debug.Log( "message" + ex ) catch Debug.LogException( $ex );
or
try each _delegate 
catch( ExactException ex )  
{
    Debug.Log( "message" + ex )
}   
    catch 
{
    Debug.LogException( $ex );
    break;
}
Jun 7, 2014 at 10:44 PM
Use of invocation syntax with the name of an auto-implemented event should always have included an automatic null check since invocation of an event with no subscribers is an expected condition. To add such behavior now would create an ugly situation where code which runs correctly with a new compiler would be accepted by an older compiler but crash at runtime. Using a different syntax for conditional execution might ease that situation.

As for ensuring that all members of a multicast delegate execute, the Framework offers no good pattern for that, so I don't think there's anything a language can really do. The solution is to not use Delegate.Combine, but instead use one's own means of handling delegate lists. Not only can custom ways of combining delegates better handle exceptions, but they can also properly handle contravariance in generic delegates. If a Delegate.Combine-based event with no subscribers is expecting an Action<SiameseCat> and it's given an Action<Animal>, it will accept that, but if it later receives an Action<Cat> it will fail. If the event used some other means of combining events, however, code which combines two delegates satisfying Action<SiameseCat> would yield an Action<SiameseCat>, even if that wasn't the type of either of the source delegates. That is possible with generics, but wasn't possible with the non-generic Delegate.Combine.
Jun 7, 2014 at 11:53 PM
You can always do action?.Invoke(args) instead of extending language to support 'null-propagating' invocation expressions action?(args) just to handle delegates.
Jun 8, 2014 at 2:14 PM
Your proposed syntax for delegate calls would be, if not ambiguous, at least very confusing when using with the ternary operator:
delegate1 ? (delegate2) ? (1) : 2;
// Which of these is it equivalent to?
(delegate1?(delegate2)) ? (1) : 2;
delegate1 ? (delegate2?(1)) : 2;
In deeply nested ternary operator expressions, the compiler might have to do lots of lookahead to determine if the first ?( was meant to be a call or the start of a ternary operator. Anyways, I think in general this is way too specific a use case to invent new syntax for.
Jun 8, 2014 at 6:41 PM
3llian wrote:
Your proposed syntax for delegate calls would be, if not ambiguous, at least very confusing when using with the ternary operator:
That's concern I also have with regard to the proposed ?. Although there is at present no situation where an expression may legitimately start with a ., the ?. operator would seem to preclude the possibility of any such expressions being added in future unless its definition resolves how "thing that might be null" should behave if placed before a ? which lacks a corresponding :.

On the other hand, in the situation where the left-hand operator is an event rather than a delegate, the usage case is extremely common. I don't know how many thousands of lines of code were required that could have been avoided if the C# spec had, from the get-go, specified that using method invocation syntax with an event whose return value is unused will include an automatic "no-op-if-null" check [a compiler could have required that code wishing to use the return value of an event use retValue = someEvent.Invoke(someParameters);, which would not include the automatic check]. An "invoke event if not null" syntax should have always been part of the language, and should still be added, even though it should, if added now, probably use a different syntax from delegate invocation.