C
C#4w ago
cm17

My class needs property type which can only either be string or int -not sure how to best implement

Working on a project to make a simple IT monitoring system - just for fun. I have a INormalisedDataPoint - this represents the interface for ingested Log/Metric data which has been processed and normalised. It has a property called Messages, where I intend to store a List<IMessageField> - so Messages would include things like CPU%: 95%, dataSource: my-macbook, ipAddress: ..... and so on. I will be ingesting both logs and metrics (numeric values).
Possible solutions I can think of: 1. I could just make IMessageField have a string Name and string Value , and then convert the string to a numeric type, but I'd like to figure out how to accept either type, as a learning experience. 2. I could have a NumericMessageField (which stores a number) and a StringMessageField which stores a string.
7 Replies
cm17
cm17OP4w ago
Here's my code so far:
public interface INormalisedDataPoint
{
public Guid Id { get; set; }
public DateTime Timestamp { get; init; }
public List<IMessageField> MessageFields { get; set; }
public string Source { get; set; }
}

public interface IMessageField
{
public string Name { get; set; }
// THERE SHOULD BE A 'Value' Property here too but I had to put that in the MessageField<T> class to make it work...

}

public class ValueField<T>
{
public T Value { get; set; }
}

public class MessageField<T> : IMessageField
{
public string Name { get; set; } = "";
public ValueField<T> Value { get; set; } = new ValueField<T>(); // THIS MUST BE EITHER A DOUBLE OR STRING

}

public class DataPoint
{
public Guid Id { get; set; }
public List<IMessageField> MessageFields { get; set; }


public DataPoint()
{
Id = Guid.NewGuid();

// INITIALISING WITH DUMMY DATA FOR TESTING PURPOSES
MessageFields = new() {
new MessageField<string> { Name = "source", Value = new ValueField<string> { Value = "my-macbook" } },
new MessageField<int> { Name = "CPU%", Value = new ValueField<int> { Value = 10 } }
};
}
}
public interface INormalisedDataPoint
{
public Guid Id { get; set; }
public DateTime Timestamp { get; init; }
public List<IMessageField> MessageFields { get; set; }
public string Source { get; set; }
}

public interface IMessageField
{
public string Name { get; set; }
// THERE SHOULD BE A 'Value' Property here too but I had to put that in the MessageField<T> class to make it work...

}

public class ValueField<T>
{
public T Value { get; set; }
}

public class MessageField<T> : IMessageField
{
public string Name { get; set; } = "";
public ValueField<T> Value { get; set; } = new ValueField<T>(); // THIS MUST BE EITHER A DOUBLE OR STRING

}

public class DataPoint
{
public Guid Id { get; set; }
public List<IMessageField> MessageFields { get; set; }


public DataPoint()
{
Id = Guid.NewGuid();

// INITIALISING WITH DUMMY DATA FOR TESTING PURPOSES
MessageFields = new() {
new MessageField<string> { Name = "source", Value = new ValueField<string> { Value = "my-macbook" } },
new MessageField<int> { Name = "CPU%", Value = new ValueField<int> { Value = 10 } }
};
}
}
mtreit
mtreit4w ago
What you are describing is what's called a Discriminated Union. Some languages like F# support this, but C# unfortunately does not currently have this.
mtreit
mtreit4w ago
There is a NuGet package that allows you to emulate this: https://www.nuget.org/packages/OneOf
OneOf 3.0.271
F# style discriminated unions for C#, using a custom type OneOf<T0, ... Tn> which holds a single value and has a .Match(...) method on it for exhaustive matching. Simple but powerful.
mtreit
mtreit4w ago
I have not used it however.
Pobiega
Pobiega4w ago
I'd probably use an abstract record MessageField and record StringMessageField(string Value) : MessageField;, assuming you need to serialize/deserialize this then slap on some [JsonDerivedType] and be happy
Kuurama
Kuurama4w ago
public interface INormalisedDataPoint
{
public Guid Id { get; set; }
public DateTime Timestamp { get; init; }
public List<MessageField> MessageFields { get; set; }
public string Source { get; set; }
}

public static class MessageFieldExtensions
{
public static void Print(this MessageField self)
{
switch (self)
{
case MessageField.StringMessageField(var name, var value):
Console.WriteLine($"String Field - Name: {name}, Value: {value}");
break;
case MessageField.DoubleMessageField (var name, var value):
Console.WriteLine($"Double Field - Name: {name}, Value: {value}");
break;
}
}
}

public abstract record MessageField(string Name)
{
public sealed record StringMessageField(string Name, string Value) : MessageField(Name);
public sealed record DoubleMessageField(string Name, double Value) : MessageField(Name);
}

public class DataPoint
{
public Guid Id { get; set; } = Guid.NewGuid();

public List<MessageField> MessageFields { get; set; } =
[
new MessageField.StringMessageField("source", "my-macbook"),
new MessageField.DoubleMessageField("CPU%", 10)
];
}
public interface INormalisedDataPoint
{
public Guid Id { get; set; }
public DateTime Timestamp { get; init; }
public List<MessageField> MessageFields { get; set; }
public string Source { get; set; }
}

public static class MessageFieldExtensions
{
public static void Print(this MessageField self)
{
switch (self)
{
case MessageField.StringMessageField(var name, var value):
Console.WriteLine($"String Field - Name: {name}, Value: {value}");
break;
case MessageField.DoubleMessageField (var name, var value):
Console.WriteLine($"Double Field - Name: {name}, Value: {value}");
break;
}
}
}

public abstract record MessageField(string Name)
{
public sealed record StringMessageField(string Name, string Value) : MessageField(Name);
public sealed record DoubleMessageField(string Name, double Value) : MessageField(Name);
}

public class DataPoint
{
public Guid Id { get; set; } = Guid.NewGuid();

public List<MessageField> MessageFields { get; set; } =
[
new MessageField.StringMessageField("source", "my-macbook"),
new MessageField.DoubleMessageField("CPU%", 10)
];
}
You don't have to put them into MessageField, or you can also using static MessageField; at the top of the file. using static FooCurrentNameSpace.MessageField; or
public abstract record MessageField(string Name);
public sealed record StringMessageField(string Name, string Value) : MessageField(Name);
public sealed record DoubleMessageField(string Name, double Value) : MessageField(Name);

public class DataPoint
{
public Guid Id { get; set; } = Guid.NewGuid();

public List<MessageField> MessageFields { get; set; } =
[
new StringMessageField("source", "my-macbook"),
new DoubleMessageField("CPU%", 10)
];
}
public abstract record MessageField(string Name);
public sealed record StringMessageField(string Name, string Value) : MessageField(Name);
public sealed record DoubleMessageField(string Name, double Value) : MessageField(Name);

public class DataPoint
{
public Guid Id { get; set; } = Guid.NewGuid();

public List<MessageField> MessageFields { get; set; } =
[
new StringMessageField("source", "my-macbook"),
new DoubleMessageField("CPU%", 10)
];
}
Then you can write switch expression and switch statements depending on the case. I miss the point of INormalisedDataPoint though. But I still included it.
cm17
cm17OP2w ago
Thanks for your help everyone

Did you find this page helpful?