I want to call a function on each member of an object in C#. What is the best way to do this?
Below are some details of my exact situation, I do not believe this is necessary to answer the question but some people have asked for more info:
I have a class with a lot of members, over 100. Unfortunately, this is unavoidable as this is a simulation software package. These variables are all captured in State objects and timestamped.
A second class called a Delta represents the differences between one State and another. For example, if State1 has x, y, z = 0, 0, 0 and State2 has x, y, z = 1, 0, 0: then the Delta from State1 to State2 has x, y, z = 1, null, null.
Deltas are then serialized into protocol buffers and sent across the network, to reconstruct the current state at the other end.
States have the following methods: GetDelta(State reference) which returns the Delta between the reference State (the older state) and the State object, ApplyDelta(Delta delta) which updates the state with the information from the delta (in the example above if you called State1.ApplyDelta(delta) you would receive State2), and Interpolate(State reference, double factor) which interpolates between the reference state and the state according to the factor (a number between 0 and 1).
States are actually an abstract idea. In the code, there are many different kinds of states - for the weather, the world, various simulation objects - but they all implement an interface that requires them to implement those 3 methods.
In a trivial case implementing all of these functions is simple. Even if it's a complex case, it's simple - but it is a lot of lines of code. 100 members require 100 lines to declare the members in the state, 100 lines to declare the members in the delta, 100 lines for each of the 3 methods above - that's 500 lines of code that are potentially error-prone.
How can I call the same function on every member of an object? -
I have considered putting all the members into a dictionary, list, or array, but I don't think it would be very clean since the data is heterogeneous. I could have one collection per type, which would be a lot cleaner but would tightly couple the data type to the way it's handled.
I know this is a long post for a simple question, but obviously a lot of architectural decisions have been made already and I didn't want to present an XY problem. Nothing is set in stone, if there's a better architecture then I will consider it.
Here is some example code of how states function: ```C# interface IInterpolatable<S, D> { D GetDelta(S reference); S ApplyDelta(D delta); S Interpolate(S reference, double factor); }
class State : IInterpolatable<State, State.Delta> { public int a; public decimal b; public boolean c;
class Delta { public int? a; public decimal? b; public boolean? c; } public Delta GetDelta(State reference) { if (reference == null) { return GetDelta(new State()); } else { return new Delta { a = reference.a != a ? a : null, b = reference.b != b ? b : null, c = reference.c != c ? c : null, }; } } public State ApplyDelta(Delta delta) { if (delta == null) { return ApplyDelta(new Delta()); } else { return new State { a = delta.a ?? a, b = delta.b ?? b, c = delta.c ?? c, }; } } public State Interpolate(State reference, double factor) { if (reference == null) { return Interpolate(new Ship(), factor); } else { // Note: There are linear interpolation functions for the used types return new Ship { a = lerp(reference.a, a, factor), b = lerp(reference.b, b, factor), c = lerp(reference.c, c, factor), }; } } } ```
You can imagine the data flow as something like:
- The simulation generates a new state
- A delta between that state and the previous is generated using GetDelta
- The delta is sent across the network to the client
- The client regenerates the new state by using ApplyDelta on the previous state
- The client then interpolates between the previous to the new state using Interpolate
Of course it's more complex than that, but that's the gist of it.
EDIT: Some people were interested to know how syncing between client and server works in this model.
Deltas are wrapped in a packet that contains a timestamp, and the timestamp of the reference state. Then they are sent to the client. When the client receives the packet they send back an acknowledgment with the timestamp. The server tracks the newest timestamp acknowledged by the client and generates the delta between that acknowledged state and the state it wants to send to the client.
Example (each number is 1 timestep, network latency is 1 step):
- The server grabs new state S1, since it has not received any acks from the client it generates a delta D1 between an empty state and S1. This state is sent across the network to the client. The client is waiting.
- The server grabs S2, still no acks so D2 is generated between an empty state and S2. The client receives D1, and uses it to generate the state S1. The client sends an ack back to the server.
- The server receives the ack from the client for S1. The server grabs S3, and generates D3. Since S1 was acked D3 is the delta between S3 and S1. The client is waiting - looks like D2 never showed up!
- The server grabs S4, and again since S1 was the latest state acked, D4 is the delta between S4 and S1. The client receives D3, generates S3, and sends back an ack.
- etc
Someone gave an example: "what would the server do if x increments every state, but 5 packets fail to be sent to the client?" The answer is that since deltas are relative to something, it will first send x+1, then x+2, then x+3, then x+4, then x+5, then finally it receives an ack so it can send x+1 next.
Sorry if that isn't clear, I can provide more information to clear it up if there's a problem.