0

Yeah, I know there aren't any virtual static members in c#, but I have a problem where they would be really helpful and I can't see a good way to proceed.

I've got a standard kind of system where I send packets of data over a communication channel and get back responses. The communication system needs to know how many bytes of response to wait for, and the length of the response is fixed for each command type, so I have this code defined:

public abstract class IPacket
{
   public abstract int ReceiveLength { get; }
   public abstract byte[] DataToSend();
}

public class Command1 : IPacket
{
   public override int ReceiveLength { get { return 3; } }
   public Command1() { }
}

public class Command2 : IPacket
{
   public override int ReceiveLength { get { return DataObject.FixedLength; } }
   public Command2(int x) { }
}

public class Command3 : IPacket
{
   static DataHelperObject Helper;
   public override int ReceiveLength { get { return Helper.DataLength(); } }
   static Command3()
   {
      Helper = new DataHelperObject();
   }
   public Command3(really long list of parameters containing many objects that are a pain to set up) { }
}

Notice that in each case, ReceiveLength is a fixed value - sometimes it's a simple constant (3), sometimes it's a static member of some other class (DataObject.FixedLength) and sometimes it's the return value from a member function of a static member (Helper.DataLength()) but it's always a fixed value.

So that's all good, I can write code like this:

void Communicate(IPacket packet)
{
   Send(packet.DataToSend());
   WaitToReceive(packet.ReceiveLength);
}

and it works perfectly.

But now I would like to output a summary of the packets. I want a table that shows the command name (the class name) and the corresponding ReceiveLength. I want to be able to write this (pseudo)code:

foreach (Class cls in myApp)
{
   if (cls.Implements(IPacket))
   {
      Debug.WriteLine("Class " + cls.Name + " receive length " + cls.ReceiveLength);
   }
}

But of course ReceiveLength requires an object.

I don't think I can use attributes here, c# won't let me say:

[PacketParameters(ReceiveLength=Helper.DataLength())]
public class Command3 : IPacket
{
   static DataHelperObject Helper;
   static Command3()
   {
      Helper = new DataHelperObject();
   }
   public Command3(really long list of parameters containing many objects that are a pain to set up) { }
}

because custom attributes are created at compile time (right?), long before the static constructor gets called.

Constructing objects of each type isn't particularly pleasant (pseudocode again):

foreach (Class cls in myApp)
{
   IPacket onePacket;
   if (cls is Command1)
      onePacket = new Command1();
   else if (cls is Command2)
      onePacket = new Command2(3);
   else if (cls is Command3)
   {
      Generate a bunch of objects that are a pain to create
      onePacket = new Command3(those objects);
   }
   Debug.WriteLine("Class " + cls.Name + " receive length " + onePacket.ReceiveLength);
}

I need ... a virtual static property.

Betty Crokker
  • 3,001
  • 6
  • 34
  • 68
  • I suggest you to use a `Dictionary>` that contains ReceiveLengthFunction for each types that you need – George Alexandria Jun 27 '17 at 20:47
  • Assuming you had this "virtual static property", how would you implement the `foreach (Class cls in myApp)` part? – Kevin Gosse Jun 27 '17 at 20:47
  • The Dictionary<> idea is interesting ... I would have each command have a static constructor that added its own information to the Dictionary ... but in my pseudocode "foreach (Class cls)" loop, how do I ensure that each class's static constructor has been called? – Betty Crokker Jun 27 '17 at 20:51
  • Possible duplicate of [Why we can not have Shared(static) function/methods in an interface/abstract class?](https://stackoverflow.com/q/330318/11683) – GSerg Jun 27 '17 at 20:53
  • Are you looking to create a summary of ACTIVE packets or just the available types of packets? It looks like IPacket should be an interface by the way. – JuanR Jun 27 '17 at 20:55
  • There's some other shared stuff that I pushed up to IPacket that turned it from an interface to an abstract class. For this example though, it would have been better to just call it an interface, you're right! – Betty Crokker Jun 27 '17 at 20:56
  • I see. Are you looking to create a summary of ACTIVE packets or just the available types of packets? – JuanR Jun 27 '17 at 20:57
  • You can ensure the execution of static constructors like this: `System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);` – Olivier Jacot-Descombes Jun 27 '17 at 20:57
  • @KevinGosse - enumerating over all classes is a standard reflection kind of thing, see https://stackoverflow.com/questions/949246/how-to-get-all-classes-within-namespace – Betty Crokker Jun 27 '17 at 20:57
  • 1
    I personally detect a code smell. Whenever you get into these kind of situations, it usually means you need to take a step back and reconsider some portion of your design. – JuanR Jun 27 '17 at 21:02
  • @BettyCrokker I just wanted to confirm you were aware of that. From there, you can use the attribute solution, with either the name of the static property to call to get the info: `[PacketParameters(ReceiveLength: "GetLength")]` (this is done by some unit test frameworks), or with the name of a definition class that you can then instantiate: `[PacketParameters(Definition: typeof(Packet3Definition))]` – Kevin Gosse Jun 27 '17 at 21:05
  • Though I'd probably do the opposite: expose the type containing the metadata, and have it reference the concrete implementation: `[PacketParameters(typeof(Command3))] public class Packet3Metadata : IPacketMetadata { ... }` – Kevin Gosse Jun 27 '17 at 21:11
  • 1
    I agree with @Juan that the fact that you got into this situation is a warning sign. Your design is broken. The question is too broad, because there are too many different ways to approach this broken design. But more generally, you have a serious problem: your interface design allows for individual _instances_ of objects to return different values for `ReceiveLength`, but you have another section of code that makes the assumption that every instance of any given class will always return the same value as any other instance. Fix the design flaw, and the rest of the problem disappears. – Peter Duniho Jun 28 '17 at 00:17
  • Question: "I can't figure out how to design this code" Answer: "Fix the design flaw". Are you in management (grin)? – Betty Crokker Jun 28 '17 at 14:18

2 Answers2

0

Just make a public static CommandX.Length property, have it return what your ReceiveLength property is now, then have ReceiveLength refer to it. To get the best of both worlds, first you need both worlds.

Quality Catalyst
  • 6,531
  • 8
  • 38
  • 62
hoodaticus
  • 3,772
  • 1
  • 18
  • 28
  • So ... the compiler wouldn't really help me ensure that each class had a Length property, and it's a bit awkward in that every class now has to have two properties instead of one, but it's doable ... – Betty Crokker Jun 27 '17 at 20:54
  • If you are definitely going to call the Length property then the compiler will absolutely make sure you defined it ;) – hoodaticus Jun 27 '17 at 20:57
  • 1
    I think I see what you're thinking ... in IPacket I would have both "internal static int Length { get; }" and "public abstract int ReceiveLength { get; }" and then in every derived class, I define Length and then say "public override int ReceiveLength { get { return Length; } }" – Betty Crokker Jun 27 '17 at 21:00
  • Exactly (except that IPacket probably doesn't get a definition for Length). I was going to suggest an attribute-based approach as well but you are using method calls in there, and attributes can only use compile-time information (constants). – hoodaticus Jun 27 '17 at 21:02
0

One solution would be to throw all compile-time safety over board and simply use reflection to access your static property like so: http://fczaja.blogspot.ch/2008/07/accessing-static-properties-using-c.html

Alternatively, you could separate out that information into a "PaketSizeManager" type which would simply have either the above-mentioned Dictionary or some switch-case statement plus some neat way to access this information from the outside, as in a public int GetSize(Type t){ .../* use dictionary or switch-case here */... } method. That way you would have encapsulated the size aspect of all your entities into a separate class.

dnickless
  • 10,733
  • 1
  • 19
  • 34