Default Executable Member Return Expressions #2563
-
Default Return ExpressionsThis proposal describes hypothetical syntax for defining a default return value for an executable member (method, property getter, returning delegate, returning lambda-expression) which has control flows that do not return a value. The use case for this would be not having to write a return statement for control flows with a common outcome, and only having to deal with special cases. The default value does not have to be a constant, rather it has access to whatever state is initially available to the method, as well as any scope-escaping variables such as ExampleDataContainer CollectData(State state, bool condition) = DataContainer.Create(state.Parameters, null, user ?? User.None)
{
if (state.Authenticated)
{
if (condition)
{
return GetData(state.Parameters);
}
else if (state.KeepOld)
{
var newData = state.OldData.Merge(GetData(state.Parameters));
newData.Sort();
return newData;
}
return; // returns default value
}
else if (state.Valid && state.Identity == Identity.Anonymous)
{
if (GetRealIdentity(state))
return; // returns default value
else
return state.GetNewData();
}
else if (state.AuthenticationBypassed || state.User is { } user)
return; // returns default value
throw new InvalidOperationException();
} The example isn't intended to be logically cohesive, but rather to showcase the use of the suggested feature. Allowing control flow to exit the body of the method would also cause the default value to be returned. For property bool Property
{
set => SetProperty(value);
get = false
{
/* ... */
}
} For lambda expressions, the feature would be used as follows. async () = await GetDataAsync() => /* ... */ |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments
-
What would be the point of doing this? It seems to me that the only point is that it would occasionally made code shorter. But this feature would not be useful that often and it would make the code less clear: when you see |
Beta Was this translation helpful? Give feedback.
-
I see your point about it being unclear when the empty return statement is used, but due to the fact that value-returning methods usually have return statements with values, I believe that having an empty one by contrast signals that some different behaviour is being triggered, so I don’t see that too much as being a major concern. A different keyword could also be used but I’m not sure what would fit best; maybe |
Beta Was this translation helpful? Give feedback.
-
You can already accomplish this behavior quite easily by declaring a temporary variable with the default value at the start of the method: DataContainer CollectData(State state, bool condition) {
val df = DataContainer.Create(state.Parameters, null, user ?? User.None);
if (state.Authenticated)
{
if (condition)
{
return GetData(state.Parameters);
}
else if (state.KeepOld)
{
var newData = state.OldData.Merge(GetData(state.Parameters));
newData.Sort();
return newData;
}
return; // returns default value
}
else if (state.Valid && state.Identity == Identity.Anonymous)
{
if (GetRealIdentity(state))
return df; // returns default value
else
return state.GetNewData();
}
else if (state.AuthenticationBypassed || state.User is { } user)
return df; // returns default value
throw new InvalidOperationException();
} I think this is much clearer than having some implicit value that would be returned when you use a |
Beta Was this translation helpful? Give feedback.
-
@HaloFour I think you missed a spot on line 15. |
Beta Was this translation helpful? Give feedback.
-
To take your specific example, you can refactor this method to avoid multiple returns easily, which also avoids the issue: DataContainer CollectData(State state, bool condition)
{
var result = DataContainer.Create(state.Parameters, null, user ?? User.None);
if (state.Authenticated)
{
if (condition)
{
result = GetData(state.Parameters);
}
else if (state.KeepOld)
{
var newData = state.OldData.Merge(GetData(state.Parameters));
newData.Sort();
result = newData;
}
}
else if (state.Valid && state.Identity == Identity.Anonymous)
{
if (!GetRealIdentity(state))
result = state.GetNewData();
}
else if (state.AuthenticationBypassed || state.User is { } user)
{
// Nothing to do - use default
}
else
{
throw new InvalidOperationException();
}
return result;
} |
Beta Was this translation helpful? Give feedback.
-
I appreciate that you say that you example isn't intended to be logically cohesive, but if we treat it as such, then there's a lot of noise (the DataContainer CollectData(State state, bool condition)
{
if (state.Authenticated)
{
if (condition) return GetData(state.Parameters);
if (state.KeepOld) return state.OldData.Merge(GetData(state.Parameters)).Sort();
return Default();
}
if (state.Valid && state.Identity == Identity.Anonymous)
return GetRealIdentity(state) ? Default() : state.GetNewData();
if (state.AuthenticationBypassed) return Default();
if (state.User is {} user) return DataContainer.Create(state.Parameters, null, user);
throw new InvalidOperationException();
DataContainer Default()
=> DataContainer.Create(state.Parameters, null, User.None);
} Adding new syntax to replace that local function would cost a lot of time and effort and would further complicate the language. It's really hard to see how that could be justified when a local function does the job just as well. |
Beta Was this translation helpful? Give feedback.
What would be the point of doing this?
It seems to me that the only point is that it would occasionally made code shorter. But this feature would not be useful that often and it would make the code less clear: when you see
return;
, you wouldn't be able to immediately tell what exactly does it mean. For these reasons, I think this feature would not be worth it.