C
C#4mo ago
hadeedji

✅ Positional record with alternate constructor

Say we have this record:
public record Point(float x, float y);
public record Point(float x, float y);
And we wanna provide a way for it to be initialized from doubles too:
public record Point(float x, float y) {
public Point(double x, double y) {
this.x = (float) x;
this.y = (float) y;
}
}
public record Point(float x, float y) {
public Point(double x, double y) {
this.x = (float) x;
this.y = (float) y;
}
}
But the compiler complains "A constructor declared in 'record' with parameter list must have 'this' constructor" It wants me to do something like this:
public record Point(float x, float y) {
public Point(double x, double y) : this(0, 0) { // The numbers here can be anything
this.x = (float) x;
this.y = (float) y;
}
}
public record Point(float x, float y) {
public Point(double x, double y) : this(0, 0) { // The numbers here can be anything
this.x = (float) x;
this.y = (float) y;
}
}
This doesn't feel right to me, seems like I'm going against the language and doesn't look like idiomatic C#. Because we're setting the fields then setting them again. But I don't see why this can't work, it should be possible to specify a constructor with some other parameters than the primary constructor, assuming all the fields get initialized. I'm looking for ways to achieve this result with idiomatic C# that isn't a lot of code, like removing the positional parameters and writing those properties manually. Thank you.
13 Replies
333fred
333fred4mo ago
Just do:
public record Point(float x, float y) {
public Point(double x, double y) : this((float)x, (float)y) {}
}
public record Point(float x, float y) {
public Point(double x, double y) : this((float)x, (float)y) {}
}
Though to be honest, this cast and second constructor is an anti-pattern, and you shouldn't do it Either use double as the types of the properties, or make the user do the cast
hadeedji
hadeedjiOP4mo ago
Yeah, got it what you're saying This was an example, in general shouldn't it be possible to have a second constructor for a record that takes some other parameter and initializes it? In some situations that would be a good idea. Say you have a color record with rgb bytes, and want to initialize it with a float vector3. I don't get why it isn't allowed What's really throwing me off is that the compiler isn't allowing something, but by adding : this(0, 0) it lets me do it. But I don't wanna add that in, looks like an anti pattern or code smell. What are your thoughts? Maybe when an anti pattern makes the thing you want to do work, the thing you want to do isn't a good idea?
333fred
333fred4mo ago
Point.x and Point.y are in scope throughout the rest of the type. They must be initialized before anything else happens Consider as an example:
public record Person(string FullName)
{
public string FirstName { get; } = FullName.Split(' ')[0];
}
public record Person(string FullName)
{
public string FirstName { get; } = FullName.Split(' ')[0];
}
That property initializer will run before the body of your alternate constructor, and FullName must have a valid value or it will null ref
hadeedji
hadeedjiOP4mo ago
Got it. Final question, how do you think you'd handle a color record struct and want to provide a constructor that inits it with a float vec3
333fred
333fred4mo ago
Say you have a color record with rgb bytes, and want to initialize it with a float vector3
This would be perfectly fine.
public record struct RGB(float R, float G, float B)
{
public RGG(Vector3 vector) : this(vector.X, vector.Y, vector.Z) {}
}
public record struct RGB(float R, float G, float B)
{
public RGG(Vector3 vector) : this(vector.X, vector.Y, vector.Z) {}
}
hadeedji
hadeedjiOP4mo ago
That makes sense. Thanks a lot!
333fred
333fred4mo ago
You're welcome. Please $close the thread if you didn't have any other questions 🙂
MODiX
MODiX4mo ago
If you have no further questions, please use /close to mark the forum thread as answered
jcotton42
jcotton424mo ago
Another approach here might also be a FromVector static method
public record struct RGB(float R, float G, float B)
{
public static RGB FromVector(Vector3 vector) => new(vector.X, vector.Y, vector.Z);
}
public record struct RGB(float R, float G, float B)
{
public static RGB FromVector(Vector3 vector) => new(vector.X, vector.Y, vector.Z);
}
I personally am liking static methods as "named constructors" more and more lately @hadeedji.
333fred
333fred4mo ago
I mean, you certainly could, but I'm not sure I would do that here
hadeedji
hadeedjiOP4mo ago
Thanks, this is what some would call a Factory, right?
333fred
333fred4mo ago
Factory method, perhaps. An actual factory is usually a separate type entirely
Angius
Angius4mo ago
In some cases, you could also use generic math to take any floating-point type, be it float or double.
public record Point<T>(T X, T Y) where T : IFloatingPoint<T>;
public record Point<T>(T X, T Y) where T : IFloatingPoint<T>;
https://learn.microsoft.com/en-us/dotnet/standard/generics/math
Want results from more Discord servers?
Add your server