When should I use a struct rather than a class in C#?

前端 未结 28 2514
予麋鹿
予麋鹿 2020-11-21 11:55

When should you use struct and not class in C#? My conceptual model is that structs are used in times when the item is merely a collection of value types. A way to

相关标签:
28条回答
  • 2020-11-21 12:07

    In addition to the "it is a value" answer, one specific scenario for using structs is when you know that you have a set of data that is causing garbage collection issues, and you have lots of objects. For example, a large list/array of Person instances. The natural metaphor here is a class, but if you have large number of long-lived Person instance, they can end up clogging GEN-2 and causing GC stalls. If the scenario warrants it, one potential approach here is to use an array (not list) of Person structs, i.e. Person[]. Now, instead of having millions of objects in GEN-2, you have a single chunk on the LOH (I'm assuming no strings etc here - i.e. a pure value without any references). This has very little GC impact.

    Working with this data is awkward, as the data is probably over-sized for a struct, and you don't want to copy fat values all the time. However, accessing it directly in an array does not copy the struct - it is in-place (contrast to a list indexer, which does copy). This means lots of work with indexes:

    int index = ...
    int id = peopleArray[index].Id;
    

    Note that keeping the values themselves immutable will help here. For more complex logic, use a method with a by-ref parameter:

    void Foo(ref Person person) {...}
    ...
    Foo(ref peopleArray[index]);
    

    Again, this is in-place - we have not copied the value.

    In very specific scenarios, this tactic can be very successful; however, it is a fairly advanced scernario that should be attempted only if you know what you are doing and why. The default here would be a class.

    0 讨论(0)
  • 2020-11-21 12:07

    Briefly, use struct if:

    1. your object properties/fields do not need to be changed. I mean you just want to give them an initial value and then read them.

    2. properties and fields in your object are value type and they are not so large.

    If that's the case, you can take advantage of structs for a better performance and optimized memory allocation as they use only stacks rather than both stacks and heaps (in classes)

    0 讨论(0)
  • 2020-11-21 12:08

    I think a good first approximation is "never".

    I think a good second approximation is "never".

    If you are desperate for perf, consider them, but then always measure.

    0 讨论(0)
  • 2020-11-21 12:09

    Nah - I don't entirely agree with the rules. They are good guidelines to consider with performance and standardization, but not in light of the possibilities.

    As you can see in the responses, there are a lot of creative ways to use them. So, these guidelines need to just be that, always for the sake of performance and efficiency.

    In this case, I use classes to represent real world objects in their larger form, I use structs to represent smaller objects that have more exact uses. The way you said it, "a more cohesive whole." The keyword being cohesive. The classes will be more object oriented elements, while structs can have some of those characteristics, though on a smaller scale. IMO.

    I use them a lot in Treeview and Listview tags where common static attributes can be accessed very quickly. I have always struggled to get this info another way. For example, in my database applications, I use a Treeview where I have Tables, SPs, Functions, or any other objects. I create and populate my struct, put it in the tag, pull it out, get the data of the selection and so forth. I wouldn't do this with a class!

    I do try and keep them small, use them in single instance situations, and keep them from changing. It's prudent to be aware of memory, allocation, and performance. And testing is so necessary.

    0 讨论(0)
  • 2020-11-21 12:10

    MYTH #1: STRUCTS ARE LIGHTWEIGHT CLASSES

    This myth comes in a variety of forms. Some people believe that value types can’t or shouldn’t have methods or other significant behavior—they should be used as simple data transfer types, with just public fields or simple properties. The DateTime type is a good counterexample to this: it makes sense for it to be a value type, in terms of being a fundamental unit like a number or a character, and it also makes sense for it to be able to perform calculations based on its value. Looking at things from the other direction, data transfer types should often be reference types anyway—the decision should be based on the desired value or reference type semantics, not the simplicity of the type. Other people believe that value types are “lighter” than reference types in terms of performance. The truth is that in some cases value types are more performant— they don’t require garbage collection unless they’re boxed, don’t have the type identification overhead, and don’t require dereferencing, for example. But in other ways, reference types are more performant—parameter passing, assigning values to variables, returning values, and similar operations only require 4 or 8 bytes to becopied (depending on whether you’re running the 32-bit or 64-bit CLR) rather than copying all the data. Imagine if ArrayList were somehow a “pure” value type, and passing an ArrayList expression to a method involved copying all its data! In almost all cases, performance isn’t really determined by this sort of decision anyway. Bottlenecks are almost never where you think they’ll be, and before you make a design decision based on performance, you should measure the different options. It’s worth noting that the combination of the two beliefs doesn’t work either. It doesn’t matter how many methods a type has (whether it’s a class or a struct)—the memory taken per instance isn’t affected. (There’s a cost in terms of the memory taken up for the code itself, but that’s incurred once rather than for each instance.)

    MYTH #2: REFERENCE TYPES LIVE ON THE HEAP; VALUE TYPES LIVE ON THE STACK

    This one is often caused by laziness on the part of the person repeating it. The first part is correct—an instance of a reference type is always created on the heap. It’s the second part that causes problems. As I’ve already noted, a variable’s value lives wherever it’s declared, so if you have a class with an instance variable of type int, that variable’s value for any given object will always be where the rest of the data for the object is—on the heap. Only local variables (variables declared within methods) and method parameters live on the stack. In C# 2 and later, even some local variables don’t really live on the stack, as you’ll see when we look at anonymous methods in chapter 5. ARE THESE CONCEPTS RELEVANT NOW? It’s arguable that if you’re writing managed code, you should let the runtime worry about how memory is best used. Indeed, the language specification makes no guarantees about what lives where; a future runtime may be able to create some objects on the stack if it knows it can get away with it, or the C# compiler could generate code that hardly uses the stack at all. The next myth is usually just a terminology issue.

    MYTH #3: OBJECTS ARE PASSED BY REFERENCE IN C# BY DEFAULT

    This is probably the most widely propagated myth. Again, the people who make this claim often (though not always) know how C# actually behaves, but they don’t know what “pass by reference” really means. Unfortunately, this is confusing for people who do know what it means. The formal definition of pass by reference is relatively complicated, involving l-values and similar computer-science terminology, but the important thing is that if you pass a variable by reference, the method you’re calling can change the value of the caller’s variable by changing its parameter value. Now, remember that the value of a reference type variable is the reference, not the object itself. You can change the contents of the object that a parameter refers to without the parameter itself being passed by reference. For instance, the following method changes the contents of the StringBuilder object in question, but the caller’s expression will still refer to the same object as before:

    void AppendHello(StringBuilder builder)
    {
        builder.Append("hello");
    }
    

    When this method is called, the parameter value (a reference to a StringBuilder) is passed by value. If you were to change the value of the builder variable within the method—for example, with the statement builder = null;—that change wouldn’t be seen by the caller, contrary to the myth. It’s interesting to note that not only is the “by reference” bit of the myth inaccurate, but so is the “objects are passed” bit. Objects themselves are never passed, either by reference or by value. When a reference type is involved, either the variable is passed by reference or the value of the argument (the reference) is passed by value. Aside from anything else, this answers the question of what happens when null is used as a by-value argument—if objects were being passed around, that would cause issues, as there wouldn’t be an object to pass! Instead, the null reference is passed by value in the same way as any other reference would be. If this quick explanation has left you bewildered, you might want to look at my article, “Parameter passing in C#,” (http://mng.bz/otVt), which goes into much more detail. These myths aren’t the only ones around. Boxing and unboxing come in for their fair share of misunderstanding, which I’ll try to clear up next.

    Reference: C# in Depth 3rd Edition by Jon Skeet

    0 讨论(0)
  • 2020-11-21 12:10

    I was just dealing with Windows Communication Foundation [WCF] Named Pipe and I did notice that it does make sense to use Structs in order to ensure that exchange of data is of value type instead of reference type.

    0 讨论(0)
提交回复
热议问题