the c# return statement

J

John Bailo

The c# *return* statement has been bothering me the past few months.

I don't like the fact that you can have different code paths in a method
and have multiple return statements. To me, it would be more orthogonal
if a method could only have one return statement.
 
T

Tom B.

John said:
The c# *return* statement has been bothering me the past few months.

I don't like the fact that you can have different code paths in a method
and have multiple return statements. To me, it would be more orthogonal
if a method could only have one return statement.

To some extent, I share this sentement :) I find that
Delphi's/Pascal's 'result' returning mechanism is more elegant, for the
most part. However, I think there are /some/ instances where returning
before the end of a method can produce more straight-forward code.

Example of Delphi's/Pascal's returning mechanism:

function Foo: Boolean;
begin
Result := True;
if FooBar then
Result := GiveMeABoolean();
DoSomethingElse();
end; // return value is value of Result

Of course, this can easily be simulated in C# (but it involves more
statements, generally):

bool Foo()
{
bool result = true;

if (fooBar)
result = GiveMeABoolean();
DoSomethingElse();

return result;
}

Or even better, in this case:

bool Foo()
{
bool result = fooBar ? GiveMeABoolean() : true;
DoSomethingElse();
return result;
}

I guess I don't mind the C-style returning mechanism so much if it's not
abused (in my opinion, according to my own personal style), but find
Delphi's/Pascal's equivalent more elegant in most cases.
 
M

mlw

John said:
The c# *return* statement has been bothering me the past few months.

I don't like the fact that you can have different code paths in a method
and have multiple return statements. To me, it would be more orthogonal
if a method could only have one return statement.

The C# language is very much based on C++ and Java, multiple return
statements are part of the language.

If you have a problem with multiple return statements, don't use them.
 
D

Donovan Rebbechi

The c# *return* statement has been bothering me the past few months.

I don't like the fact that you can have different code paths in a method
and have multiple return statements. To me, it would be more orthogonal
if a method could only have one return statement.

If you have exceptions, you can have different paths in a method. So unless
you're prepared to do without exceptions and check return codes every time
you tie your shoelaces, you're stuck with the perceived evils of multiple paths.

Cheers,
 
T

Tom B.

Donovan said:
If you have exceptions, you can have different paths in a method. So unless
you're prepared to do without exceptions and check return codes every time
you tie your shoelaces, you're stuck with the perceived evils of multiple paths.

As I say, I agree with JB to some extent in that I don't generally like
returning from methods /before/ the end, with a 'return' statement.
However, I don't feel the same with throwing exceptions since, if one's
using them correctly, one'll only be throwing them in /exceptional/
circumstances anyway, in which case one would have no desire *not* to
return from the method there and then.
 
M

Milo T.

The c# *return* statement has been bothering me the past few months.

I don't like the fact that you can have different code paths in a method
and have multiple return statements. To me, it would be more orthogonal
if a method could only have one return statement.

You mean "simpler", not "orthogonal".

And it's only simpler if the logic in the function is simple. Once it gets
complex, it makes more sense to return at the point a return is required -
not waiting until later and setting up all kinds of flags and variables to
stash state until you hit the return statement.

Unless you're suggesting that a goto statement to the return would be ok?
 
D

Donovan Rebbechi

You mean "simpler", not "orthogonal".

And it's only simpler if the logic in the function is simple. Once it gets

To me, very long and complex functions in an OOP language usually indicates
messy coding. One can usually break up the logic into smaller functions.
If the complexity of passing all the local data to these functions is
prohibitive (this is one of the main reasons functions aren't subdivided,
especially in C), it's usually a sign that you need group some of this data
into objects.
complex, it makes more sense to return at the point a return is required -
not waiting until later and setting up all kinds of flags and variables to
stash state until you hit the return statement.

Unless you're suggesting that a goto statement to the return would be ok?

Nah. He was thinking of wrapping the function body in a try block.

Cheers,
 
?

=?iso-8859-1?Q?Lin=F8nut?=

Error BR-549: MS DRM 1.0 rejects the following post from Tom B.:
John Bailo wrote:

Example of Delphi's/Pascal's returning mechanism:

function Foo: Boolean;
begin
Result := True;
if FooBar then
Result := GiveMeABoolean();
DoSomethingElse();
end; // return value is value of Result

Get that ugly crap offa my screen!

<hyperventilates>

I'm no longer using Builder.
I'm no longer using Builder.
I'm no longer using Builder.
 
?

=?iso-8859-1?Q?Lin=F8nut?=

Error BR-549: MS DRM 1.0 rejects the following post from Donovan Rebbechi:
If you have exceptions, you can have different paths in a method. So unless
you're prepared to do without exceptions and check return codes every time
you tie your shoelaces, you're stuck with the perceived evils of multiple paths

Multiple paths can be evil in complete code or code that is dominated by such
paths. I can still remember unrolling some Cosmic FORTRAN code into decent
structured C code.

If a routine fits on the screen, you can pretty much get away with anything.
 
J

Jon Skeet [C# MVP]

[Removed comp.os.linux.advocacy, which is completely irrelevant here.]

John Bailo said:
The c# *return* statement has been bothering me the past few months.

I don't like the fact that you can have different code paths in a method
and have multiple return statements. To me, it would be more orthogonal
if a method could only have one return statement.

I disagree. For instance, here's a piece of code I've been using in a
thread on the C# newsgroup recently:

public static bool IsDecimal (string data)
{
bool gotPoint = false;

foreach (char c in data)
{
if (c=='.')
{
if (gotPoint)
{
return false;
}
gotPoint = true;
continue;
}
if (c < '0' || c > '9')
{
return false;
}
}
return true;
}

(This isn't designed to be culture-sensitive, or work with +/- etc -
it's just an example.)

To avoid multiple returns, you end up having to introduce another local
variable, and break out of the loop when you know what the result is
going to be. You could do the breaking part using a while loop instead,
but then the iteration becomes less readable. Either way, you've still
got the extra local variable.

The way I see it, "return" is a powerful way of saying "If you've got
here, you know the result of the method - none of the rest of the
code/state is relevant."

As others have mentioned, if you don't like using it, you don't have to
- you can always put that extra local variable in, along with all the
breaks you need, and then return at the end of the method. No-one's
forcing you to write code like the above - but I'm glad I'm not being
forced *not* to write it, either.
 
M

Milo T.

To me, very long and complex functions in an OOP language usually indicates
messy coding. One can usually break up the logic into smaller functions.
If the complexity of passing all the local data to these functions is
prohibitive (this is one of the main reasons functions aren't subdivided,
especially in C), it's usually a sign that you need group some of this data
into objects.

Yes, generally I would agree but sometimes breaking things down into
objects is something you don't want to or cannot do (for performance
reasons).

Besides, I find the first clearer than the second personally...

ValueType Function(EnumType a) {

switch(a)
{
case 1:
{
DoSomething();
DoSomethingElse();
return DoSomethingNew();
}
default:
ASSERT("Shouldn't reach here - bad switch value");
// intentional fallthrough
case 2:
{
DoSomething();
DoSomethingElse();
return DoSomethingNew();
}
}

}

ValueType Function(EnumType a) {

ValueType v;

switch(a)
{
case 1:
{
DoSomething();
DoSomethingElse();
v=DoSomethingNew();
break;
}
default:
ASSERT("Shouldn't reach here - bad switch value");
// intentional fallthrough
case 2:
{
DoSomething();
DoSomethingElse();
v=DoSomethingNew();
break;
}
}

return v;

}
 
J

John Bailo

Milo said:
Yes, generally I would agree but sometimes breaking things down into
objects is something you don't want to or cannot do (for performance
reasons).

Besides, I find the first clearer than the second personally...

ValueType Function(EnumType a) {

switch(a)
{
case 1:
{
DoSomething();
DoSomethingElse();
return DoSomethingNew();
}
default:
ASSERT("Shouldn't reach here - bad switch value");
// intentional fallthrough
case 2:
{
DoSomething();
DoSomethingElse();
return DoSomethingNew();
}
}

}

ValueType Function(EnumType a) {

ValueType v;

switch(a)
{
case 1:
{
DoSomething();
DoSomethingElse();
v=DoSomethingNew();
break;
}
default:
ASSERT("Shouldn't reach here - bad switch value");
// intentional fallthrough
case 2:
{
DoSomething();
DoSomethingElse();
v=DoSomethingNew();
break;
}
}

return v;

}

See, that's exactly what I don't like -- having conditional multiple
returns.

What I propose is the following for a method declaration, which is normally:


public type funname(type param)

would now be

public @returnVar=null type funname(type param)

in this case, @returnVar is declared as part of the general declaration and
so is inherent in the the method, as well as it's default value. thus
funname can never not return a value or have an ambiguous code path. yet,
within the method the value of @returnVal can be continuously redefined.
 
W

Whelp

Tom said:
Or even better, in this case:

bool Foo()
{
bool result = fooBar ? GiveMeABoolean() : true;
DoSomethingElse();
return result;
}

I like:
bool Foo()
{
bool result = !foobar || GiveMeABoolean();
DoSomethingElse();
return result;
}

ACK on the C/Pascal style thing.

Regards,
Whelp
 
C

cody

The c# *return* statement has been bothering me the past few months.
I don't like the fact that you can have different code paths in a method
and have multiple return statements. To me, it would be more orthogonal
if a method could only have one return statement.


even in pascal/delphi you can prematurely exit a method which often is
useful.
 
D

Donovan Rebbechi

Yes, generally I would agree but sometimes breaking things down into
objects is something you don't want to or cannot do (for performance
reasons).

Seriously ? I mean, can't a switch be performed via table lookup as opposed to
an if/then/else ?

switch is one of my least favourite constructs in code. It can always be
replaced with table lookup or polymorphism. Both of these would better
address the "bad switch value" issue.

Cheers,
 
M

Milo T.

Seriously ? I mean, can't a switch be performed via table lookup as opposed to
an if/then/else ?

switch is one of my least favourite constructs in code. It can always be
replaced with table lookup or polymorphism. Both of these would better
address the "bad switch value" issue.

Well, any good compiler will take a switch statement and turn it into a
lookup table for you.

As for polymorphism - in C#, I guess there's no real impact. In C++, you're
now carrying around a vtable.

*shrugs* All depends on what you're optimizing for, I guess.
 
M

Milo T.

switch is one of my least favourite constructs in code. It can always be
replaced with table lookup or polymorphism. Both of these would better
address the "bad switch value" issue.

Oh, and the other reason to use a switch:

Object* ConstructMeAnObject(unsigned short idvalue)
{
switch (idvalue)
{
case Square:
return new Square();
case Circle:
return new Circle();
case Pentagon:
return new Pentagon();
case Sheep:
return new Sheep();
}
}

.... which is the kind of code that gets important pretty quickly once you
start worry about serialization of data over a network, or to and from
files.

Sure, you can craft your own table of values and function pointers, but
it's not as readable - and will never be as readable until C++/C/C# gets
better support for table-driving data structures.

Although I'd find better table-driven support a bit of a boon right now. At
the moment I have to tie a UI control to an internal lookup ID, to a member
on a network serialized structure, to a scaling ID, to an output data
structure.

Sure would be nice to be able to just fill in a table, and get that to
spill out all of the interconnections instead of having to hack them in
manually.
 
D

Donovan Rebbechi

Oh, and the other reason to use a switch:

Object* ConstructMeAnObject(unsigned short idvalue)
{
switch (idvalue)
{
case Square:
return new Square();
case Circle:
return new Circle();
case Pentagon:
return new Pentagon();
case Sheep:
return new Sheep();
}
}

... which is the kind of code that gets important pretty quickly once you
start worry about serialization of data over a network, or to and from
files.

Better, because this kind of code usually consolidates the switch in the one
place. Note that this sort of construction doesn't come up that many times
in the same program, because you only have one of these for each family of
classes.
Sure, you can craft your own table of values and function pointers, but
it's not as readable - and will never be as readable until C++/C/C# gets
better support for table-driving data structures.

see std::map. The clean way to implement the above in c++ is to have a
std::map<int, Object*> and use a clone() method to get your prototype.
The translation unit that defines the subclass in question takes care of
inserting the instance into the map.

e.g.

template <class T> insert_into_map {
insert_into_map(std::string s) {
T* x = new T;
the_map().insert(s,x); // the_map is the global table
}
};

class Circle {

....

};
namespace {
insert_into_map <Circle> x("Circle);
};

Object* make_object(const std::string & id){
std::map<std::string,Object*>::iterator it = the_map().find(id);
if (it==the_map().end())
return NULL;
else return it->second->clone();
}

This also has the advantage that you can populate the map at runtime if you
dynamically load the code that defines circle (so you get runtime plugin
support)


Cheers,
 
I

Ian Hilliard

Oh, and the other reason to use a switch:

Object* ConstructMeAnObject(unsigned short idvalue)
{
switch (idvalue)
{
case Square:
return new Square();
case Circle:
return new Circle();
case Pentagon:
return new Pentagon();
case Sheep:
return new Sheep();
}
}

... which is the kind of code that gets important pretty quickly once you
start worry about serialization of data over a network, or to and from
files.

Sure, you can craft your own table of values and function pointers, but
it's not as readable - and will never be as readable until C++/C/C# gets
better support for table-driving data structures.

Although I'd find better table-driven support a bit of a boon right now. At
the moment I have to tie a UI control to an internal lookup ID, to a member
on a network serialized structure, to a scaling ID, to an output data
structure.

Sure would be nice to be able to just fill in a table, and get that to
spill out all of the interconnections instead of having to hack them in
manually.

This is a good one. If the idvalue is other than Square, Circle, Pentagon
or Sheep, then you have no return. To that I have to say that one of the
tennits of good coding, states that I cannot trust an object where I do
not control the memory in which it resides. The returned object is not in
the callers control and hence may be invalid. This may be fast but it is
very dangerous. There are far better patterns were reliability is required
and isn't that all the time.

Ian
 
D

Daniel O'Connell [C# MVP]

Donovan Rebbechi said:
see std::map. The clean way to implement the above in c++ is to have a
std::map<int, Object*> and use a clone() method to get your prototype.
The translation unit that defines the subclass in question takes care of
inserting the instance into the map.

e.g.

template <class T> insert_into_map {
insert_into_map(std::string s) {
T* x = new T;
the_map().insert(s,x); // the_map is the global table
}
};

class Circle {

....

};
namespace {
insert_into_map <Circle> x("Circle);
};

Object* make_object(const std::string & id){
std::map<std::string,Object*>::iterator it = the_map().find(id);
if (it==the_map().end())
return NULL;
else return it->second->clone();
}

This also has the advantage that you can populate the map at runtime if
you
dynamically load the code that defines circle (so you get runtime plugin
support)

IMHO, anything solution to a problem so simple as choosing a path from 3-5
static options that uses templates and complex logic is really overthinking
the problem. The resultant code is going to be slower, the time to fix bugs
is going to increase, and the code understandability goes down.

Also, in the case you want dynamic lookups, I would recommend a factory
approach over a cloning approach. That not only allows you dynamic lookups,
but also allows dynamic parameters and saves you from having to design a
class so it can be instantiated without actually doing its work.
The equivilent C# code(using generics partially) would be...

public interface IObjectFactory
{
object CreateObject(object[] arguments);
}

public class MapLookupClass
{
Dictionary<short,IObjectFactory> dict = new Dictionary<short,object>();


public vpod InsertIntoMap(short id, IObjectFactory factory)
{
dict.Add(id,objectFactory);
}

public MakeObject(short id, object[] arguments)
{
IObjectFactory fac;
fac = dict[id];
return fac.CreateObject(arguments);
}
}
It is more readable than the C++ approach, IMHO, but it is still
considerably less readable than a simple switch.
It doesn't strive to achieve automatic registration as yours does, although
I think that is possible I don't have a compiler on hand to write something
to test it and get the syntax right ATM. I would probably use attributes and
reflection anyway.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top