开发者

Is it better to use out for multiple output values or return a combined value type?

For instance, along the lines of:

public bool Intersect (Ray ray, out float dista开发者_如何学Gonce, out Vector3 normal)
{

}

vs

public IntersectResult Intersect (Ray ray)
{

}

public class IntersectResult
{
    public bool Intersects {get;set;}
    public float Distance {get;set;}
    public Vector3 Normal {get;set;}
}

Which is better both for clarity, ease of use, and most importantly performance.


I would use a combined type and I'll tell you why: because computation of a value should return the value, not mutate a bunch of variables. Mutating a bunch of variables doesn't scale once you need more than one of them mutated. Suppose you want a thousand of these things:

IEnumerable<Ray> rays = GetAThousandRays();
var intersections = from ray in rays 
                    where Intersect(ray, out distance, out normal)
                    orderby distance ...

Executing the query is now repeatedly mutating the same two variables. You're ordering based on a value that is being mutated. This is a mess. Don't make queries that mutate things; it is very confusing.

What you want is:

var intersections = from ray in rays 
                    let intersection = Intersect(ray)
                    where intersection.Intersects
                    orderby intersection.Distance ...

No mutation; manipulate a sequence of values as values not as variables.

I also would be inclined to maybe get rid of that Boolean flag, and make the value an immutable struct:

// returns null if there is no intersection
Intersection? Intersect(Ray ray) { ... }

struct Intersection 
{
    public double Distance { get; private set; }
    public Vector3 Normal { get; private set; }
    public Intersection(double distance, Vector3 normal) : this()
    {
        this.Normal = normal;
        this.Distance = distance;
    }
} 


I would use a combined type.

With an object you can attach behaviour, and return an arbitrarily complex object. You may wish to refactor your method in the future, and change the return values. By wrapping them in a return object and adding behaviour to that object, this refactoring can become largely transparent.

It's tempting to use tuples and the like. However the refactoring effort becomes a headache after a while (I'm speaking from experience here, having just made this mistake again)


It's best to return a combined type. At the very least your method signature is cleaner and there's less work for the programmer to do when calling it. EG: you don't have to declare and initialize variables, which clutters the calling code.


Some people say it's a matter of preference, but I think that returning a complex object is better not only for clarity, but for maintenance as well.

If you ever needed to add another bit of data as output, you would have to change the method signature in addition to adding the code for the extra response; not so with a complex output type. In addition, it's harder to mock (for unit testing) output parameters. It also seems to break the 'simple' philosophy - you're having to go through the trouble of getting the output parameters set up when you only need one piece of input. The strength of an OOP language is making types - go with that route in my opinion.

The performance difference between the two is negligible.


You could use a Tuple instead of creating the IntersectResult class as an alternative.

Reference:

http://msdn.microsoft.com/en-us/library/system.tuple.aspx

However, I would definitely lean toward returning a complex type over out parameters.


Just for the record: It's hard to find a good example of the magic trio (clarity, ease of use, and performance).

Obviously the second example provides the first two, while the first one provides better performance. Whether this is negligible, I do not know. Maybe run a benchmark test and find out.

What I can tell you is that if you make your result type a small-sized struct, you can still save some performance points, since allocating on the heap is more expensive than on the stack. Again, copying the data from a value type can outweigh the heap-allocation penalty, but maybe not if you keep it small enough. Furthermore, you probably want to make that type immutable.


Option 2 is probably the best solution if it best captures your domain. In which case your new type will get used in many other places as it represents a logical entity.


That depends entirely on how coherent the concept of an IntersectResult is.

Technically there is no reason to prefer one over the other.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜