开发者

Declaring/passing structs vs. declaring/passing individual values

开发者 https://www.devze.com 2022-12-20 13:22 出处:网络
I have two questions, actually. But they are very closely related. Suppose I have a simple struct like this:

I have two questions, actually. But they are very closely related.

Suppose I have a simple struct like this:

public struct TradePrice {
    public float Price;
    public int Quantity;

    public TradePrice(float price, int quantity) {
        Price = price;
        Quantity = quantity;
    }
}

Question 1

Now if I have code somewhere that needs to access these values, is there any difference between doing this:

TradePrice tp = new TradePrice(50.0f, 100);

And this:

float p = 50.0f;
int q = 100;

I feel like the first, since it is calling a constructor, should have something lik开发者_运维问答e the overhead of a method call. But that is just a guess.

Question 2

The answer to the above question actually probably answers this question as well, but perhaps there is some subtlety that makes these scenarios different; so I will ask anyway. Suppose I have a struct which itself has members that are struct types. Like this:

public struct BidAsk {
    public TradePrice Bid;
    public TradePrice Ask;
}

Then would there be any difference between these three methods?

Reset(TradePrice bid, TradePrice ask) {
    Bid = bid;
    Ask = ask;
}

Reset(float bidPrice, int bidQuantity, float askPrice, int askQuantity) {
    Bid = new TradePrice(bidPrice, bidQuantity);
    Ask = new TradePrice(askPrice, askQuantity);
}

Reset(float bidPrice, int bidQuantity, float askPrice, int askQuantity) {
    Bid.Price = bidPrice;
    Bid.Quantity = bidQuantity;
    Ask.Price = askPrice;
    Ask.Quantity = askQuantity;
}

I am inclined to think that there is some overhead to the constructor for a struct, and so even though the same amount of data is being passed/set in the above methods, the first and last ones might be slightly more efficient. Is this true? And are the first and last methods even different?

For the record, I'm not asking this because I mistakenly believe this is contributing to a performance bottleneck or that it particularly matters from a design standpoint (though perhaps someone will surprise me by arguing that it does). I'm asking because I'm just curious.


You've got bigger problems to sort out first. This code smells bad.

Problem #1: you're using a binary floating point number to represent a financial quantity. Binary floating point numbers are designed to represent scientific quantities where tiny errors in the last few bits of precisions are unimportant. They are extremely unsuitable for representing exact financial quantities. If you are representing financial quantities always use decimal, never use float.

Problem #2: Supposing that you had a good reason to use binary floating point, don't use float. Use double. Modern hardware is optimized to do double precision arithmetic, not single precision; there are situations in which single precision is actually slower. Of course, double takes up twice as much memory as float, but unless you've got a few million of these in memory at once, it really doesn't matter. And besides, you should be using decimal anyway, which is four times larger than float.

Problem #3: You have a mutable value type. Mutable value types are pure evil. It is so easy to accidentally cause bugs with mutable value types. Avoid avoid avoid.

Problem #4: Is that really the right name for the struct? I would think that the "trade price" would be represented by the Price property. Is there a better name for this thing that more clearly describes what it represents? Does it represent some clear concept in your business domain?

Problem #5: You do no validation on the arguments passed in to a public constructor of a public struct. This seems hazardous. What if someone tries to trade -100 shares at a price of -100000.00 a share. What happens? Nothing good, I'll bet.

Problem #6: The default value of a struct should always be a valid instance of the struct. Is it? The default instance of this struct is Price = 0, Quantity = 0. Does that actually make sense? If it doesn't, then don't make this a struct. Make it a class.

Problem #7: Is this logically a value type in the first place? Why is it a struct? Is this something that you feel should be treated logically as a value, like the number 12, or logically as something you can refer to "the same thing" in multiple locations, like a customer? If it is not logically a value, don't use a value type.

Assuming the name is correct and that this is logically a value type, your code should be something like:

public struct TradePrice 
{ 
    public decimal Price {get; private set;}
    public int Quantity {get; private set; }
    public TradePrice(decimal price, int quantity) : this()
    { 
        if (price < 0m) throw new ...
        if (quantity < 0) throw new ...
        this.Price = price; 
        this.Quantity = quantity; 
    } 
} 

As for your actual questions: there is no logical difference. There might be a performance difference. I haven't got the faintest idea of whether there is a measurable performance difference here or not. You've already writen the code both ways. Get a stopwatch, run it a billion times both ways, and then you'll know the answer to your question.


Passing structs instead of individual values may lead to better maintainability because if you ever need to change the struct definition the code change impact will be lower.


A struct with e.g. three fields behaves much like a group of three variables stuck together with duct tape. Allocating a field or variable of a structure type allocates space for those three individual fields, and passing a value of a structure type by value has essentially the same run-time cost as would passing the three fields separately.

The primary differences between a struct versus a group of individual variables are: (1) a single array of a struct type holds all of the struct's fields in each element. Thus, if MyPoint3d is a struct, one array of 50 elements will hold all 50 values of x, y, and z. If one were using separately fields for the different coordinates, one would have to create three different arrays; (2) passing a struct of any size as a ref parameter only requires passing a single reference. Code which accepts a ref parameter of a structure type (e.g. MyPoint3d) will know that the x, y and z members accessed via that reference all belong to the same struct instance. By contrast, if code accepted a ref parameter for an x coordinate, another for y, and another for z, it would have no way of knowing if they were in fact the x, y, and z coordinates of a single point.

Incidentally, it's fine for structs to have exposed public fields (if a struct is supposed to represent a collection of variables, there's no better way to show that) but it's bad for struct methods to modify this. It's better to use a static method which accepts a ref parameter of the structure type. Compilers will squawk if an attempt is made to pass a read-only structure as a declared ref parameter; by contrast, they will not squawk if an attempt is made to pass a read-only structure to a method which modifies this. The generated code won't work, but it won't generate any compiler diagnostic. Using an explicit ref parameter will ensure that code which won't work, won't compile.

0

精彩评论

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

关注公众号