3

I am looking at implementing an async function returning a result struct, the result itself should be immutable and i am considering returning an read-only struct instead of class.

Looking at some latest Microsoft apis source code i can see that read-only structs are widely used so my question is whether this would be the correct way to go. From reading numerous articles this is not clear, many point out that struct should be only used for small size values? Since struct will be read-only as i understand there will be no copying ? My return structure will have 2-5 int values and a reference to ReadOnlyMemory buffer.

The purpose is of course to have most efficient low memory footprint code.

Thanks.

NullReference
  • 862
  • 1
  • 11
  • 27
  • 2
    Aren't structs always passed by value (copying)? Does the read-only accessor change that? – digital_hog Sep 14 '20 at 07:27
  • As digital_hog already mentioned, when you return a struct from a method, it´s a copy of the one created **within** that method anyway, because structs are passed by value. `readonly` would make that **copy** immutable. – MakePeaceGreatAgain Sep 14 '20 at 07:36
  • you could also take a look at `ValueObject` if the objects are bigger – lukaszberwid Sep 14 '20 at 07:59

1 Answers1

2

That's perfectly valid, certainly. As you seem to know, the struct part helps avoid some allocations, and the readonly part avoids all the pain commonly associated with mutable value-types. As a return value, I wouldn't even hesitate - it's fine. As an input to a "sync" method, you might want to consider adding the in modifier so that a large value-type isn't unnecessarily copied on the stack (which can otherwise make value-types more expensive than reference-types). However, in your specific case the "async" is often going to be a barrier to in usage (you can't use in and async in combination). For genuinely understanding whether using a struct makes it faster, you need to benchmark your specific scenario, as the details matter a lot.

You might also need to be aware of the allocation scenarios in async code. For example, you might want to think about ValueTask<T> results (rather than Task<T> results), and if the result is commonly synchronous (and occasionally truly async), optimizing for that to avoid the state machine - which can be summarised by this approach. Depending on the scenario, you might also want to play with tools like PooledAwait (which is available on NuGet).

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks! I am already using ValueTask as the executed method might have IO calls in some cases , there is no need to have any In parameters so far so i guess your answer clarifies that this is correct way to go and optimize my code. Ofc i will do some benchmarks :) – NullReference Sep 14 '20 at 07:59
  • @NullReference IO-related scenarios (for example, stream/pipe parsing) is a case where the "manually optimize for sync" (i.e. don't make the public method `async`, but *defer* to an `async` helper when the data isn't available) can really help – Marc Gravell Sep 14 '20 at 08:01
  • Alright , will have that in mind. I am not sure if this allowed by site policy but i still want to ask, i have this question open https://stackoverflow.com/questions/63848351/pipelines-buffer-preserving-until-processing-is-complete , i am not even sure if this achievable so it would be great to have an expert take on this, – NullReference Sep 14 '20 at 08:07
  • @NullReference no problem; that question is right up my street (I was involved in pipelines before they were called pipelines ;p) – Marc Gravell Sep 14 '20 at 08:10
  • Yep, i know you also involved with C# ProtoBuf implementation, its really helped me with some of my projects so thanks a lot for all your work :) – NullReference Sep 14 '20 at 08:12