0

I'm implementing a "save/load" function in my app. There are various types of Vertex object that have a lot of static fields, but apparently you can't serialize static fields. I'm making a class whose instance contains these fields, and to avoid too much code to populate these fields I'm implementing them as properties.

However, the syntax to do this seems too bulky, so I'm looking for a way to simplify this:

public Color CircleFillColor { get => CircleVertex.fillColor; set => CircleVertex.fillColor = value; }
public Color SquareFillColor { get => SquareVertex.fillColor; set => SquareVertex.fillColor = value; }
public Color TriangleFillColor { get => TriangleVertex.fillColor; set => TriangleVertex.fillColor = value; }

If we had macros in C#, I could write something like this:

#define passthru(F) { get => F; set => F = value; }
public Color CircleFillColor passthru(CircleVertex.fillColor)
public Color SquareFillColor passthru(SquareVertex.fillColor)
public Color TriangleFillColor passthru(TriangleVertex.fillColor)

Is there any shorthand for properties like this?

Danya02
  • 1,006
  • 1
  • 9
  • 24
  • What is `Vertex` object? Why can't you change those to instance members and serialize `Vertex` instance as usual? – Sinatr Jan 06 '20 at 08:26
  • 1
    T4 template could be a way to do what you want. `partial class` what will contain generated properties using reflection from static fields. – Sinatr Jan 06 '20 at 08:29
  • you can do the serialization via reflection instead, this answer has an example of how you might want to do that https://stackoverflow.com/a/5261678/10431 – Keith Nicholas Jan 06 '20 at 08:30
  • @Sinatr A `Vertex` has a X/Y coordinate, a `PictureBox`, a method to draw its shape on the `PictureBox` and some logic for drag-and-dropping. Every `CircleVertex` must have a single `FillColor`, as should each `SquareVertex` and each `TriangleVertex`, so a `static` field seems like a natural choice. The data and the presentation are welded a bit too close for my liking, but that's what the spec requires. – Danya02 Jan 06 '20 at 08:57
  • @KeithNicholas That could work, but I'd prefer not to do the reflection at runtime, because the functions will be available through an API and there may be legitimate cases where you might need to save the app state very often. – Danya02 Jan 06 '20 at 09:05
  • very very very unlikely reflection is going to be slower than the actual saving. – Keith Nicholas Jan 06 '20 at 09:10
  • @KeithNicholas Probably so, but compile-time reflection has some other benefits. For example I think it's nice to be able to statically derive your output file's structure from the class structure. I'm doing the serialization in XML, so if I do the reflection at compile-time I can hook a XML schema generation script into this as well, perhaps using the T4 approach. – Danya02 Jan 06 '20 at 09:22
  • 1
    Depends on your preferences. How will you react on changes to your static class, any generated code or manually written code would be static - not automaticly updated. Reflection is some programming itself, worth the effort only, if you either want this automatism, or you have more than 20 or 50 properties. In writing 20 stupid lines, you make less mistakes than in writing 20 intelligent lines - but they don't look very elegant. You want your code to look good ? Really a matter of preferences. – Holger Jan 06 '20 at 09:26
  • @Holger That's true, except that I started writing the 20 stupid lines, got bored and started copy-pasting the lines, and didn't notice for a while that I hadn't edited some of the properties involved -- so I wouldn't say that you make less mistakes in stupid lines because they're less debuggable. I don't really need the automation, so I can re-run the code generation before compiling so it always stays up-to-date -- in fact kinda like the C preprocessor macros I found myself lacking here. – Danya02 Jan 06 '20 at 09:34
  • No, Makros don't exist, and I think this makes the code more readable. You cannot hide any syntactical construct by a Macro, if you read code, you know what is a function call, and what isn't. However for your XML just an idea: you don't need to generate properties, to serialize something with `XmlSerializer`. You can add a Method signed with the `XmlAnythingAttribute` (google for details), and this will create XmlElements from any source you want. You should use Reflection than. But you don't need the XmlSerializer to use Reflection to analyze your new class a second time. – Holger Jan 06 '20 at 10:02
  • @Sinatr I've ended up using the T4 method. Would you be interested in submitting it as an answer so I could accept it? – Danya02 Jan 10 '20 at 21:55
  • @Danya02, if you think this topic might be useful for future readers, then it's the best if [you post the answer](https://stackoverflow.com/help/self-answer). – Sinatr Jan 12 '20 at 12:28

2 Answers2

1

There isn't a direct equivalent to macros. An alternative would be to encapsulate the calls which yields minimally shorter properties at the expense of two additional methods and moves the affected fillColor property to a single method each instead of repeating it twice in every property.

public Color CircleFillColor { get => GetColor(CircleVertex); set => SetColor(CircleVertex, value); }
public Color SquareFillColor { get => GetColor(SquareVertex); set => SetColor(SquareVertex, value); }
public Color TriangleFillColor { get => GetColor(TriangleVertex); set => SetColor(TriangleVertex, value); }

private Vertex GetColor(Vertex vertex)
{
  return vertex.fillColor;
}

private void SetColor(Vertex vertex, Color color)
{
  vertex.fillColor = color;
}
Lennart
  • 9,657
  • 16
  • 68
  • 84
  • One of my thoughts was similar, but as written it doesn't work because `CircleVertex` and friends are types, not instances, while `GetColor` and `SetColor` are expecting a `Vertex` instance. This code would work great on non-static fields, but I can't see how this would work on static ones. – Danya02 Jan 07 '20 at 20:22
  • @Danya02 I don't understand "CircleVertex and friends are types, not instances". How would you assign a property value to a type? Do you mean they are static or abstract? The way I understand the original question, e.g. `CircleVertex` is a static field that has the static property `fillColor`. – Lennart Jan 08 '20 at 12:31
  • I'm not sure what you mean. `WhateverVertex` is a type, so you can do `Vertex v = new WhateverVertex()`, and it has a `fillColor` static field. Because static fields/properties cannot be declared a part of an abstract class (of which `Vertex` is one), so there's actually no static guarantee that if you define `class MyVeryOwnVertex : Vertex`, then `MyVeryOwnVertex.fillColor` would be valid. Because there's no static guarantee of this, but every `Vertex` subclass will in practice have a `fillColor`, AFAIK I can't use any of the nice OOP features to do this. – Danya02 Jan 10 '20 at 18:47
0

I ended up using T4 text templates included in Visual Studio. As my goal was to reduce the amount of code that I needed to write by hand, so that there would be less of a chance to make a mistake while e.g. copy/pasting code.

In my project there are types of CircleVertex, SquareVertex and TriangleVertex, each of which has a separate fillColor and strokeColor. This can be thought of as a nested loop, and T4 allows to write it this way.

As an example, here's the code I ended up using. The relevant syntax parts are that things in <# ... #> is C# code, and <#= foo #> is replaced with the value of foo when this line is encountered.

<#
String[] shapes = new String[] {"Circle","Square","Triangle"};
String[] colors = new String[] {"fillColor", "strokeColor"};
foreach(var shape in shapes){
    foreach(var color in colors){
#>
[XmlElement(Type = typeof(XmlColor))]
public Color <#=shape#>_<#=color#>
{
    get => <#=shape#>Vertex.<#=color#>;
    set => <#=color#>Vertex.<#=shape#> = value; 
}
<#}#>

This expands to:

[XmlElement(Type = typeof(XmlColor))]
public Color Circle_fillColor
{
    get => CircleVertex.fillColor;
    set => CircleVertex.fillColor = value;
}
[XmlElement(Type = typeof(XmlColor))]
public Color Circle_strokeColor
{
    get => CircleVertex.strokeColor;
    set => CircleVertex.strokeColor = value;
}
[XmlElement(Type = typeof(XmlColor))]
public Color Square_fillColor
{
    get => SquareVertex.fillColor;
    set => SquareVertex.fillColor = value;
}

...snip...

[XmlElement(Type = typeof(XmlColor))]
public Color Triangle_strokeColor
{
    get => TriangleVertex.strokeColor;
    set => TriangleVertex.strokeColor = value;
}
Danya02
  • 1,006
  • 1
  • 9
  • 24