Struct augmentation #5897
Replies: 10 comments 59 replies
-
Can this be pass by ref ? Suppose there is void DoSomething(ref Point2D point)
{
point.X = SomeValX;
point.Y = SomeValY;
}
Point3D p;
DoSomething(ref p); Possible? |
Beta Was this translation helpful? Give feedback.
-
Feels like something that would be very easily implemented with a source generator: struct Point2D : IEquatable<Point2D>
{
public Point2D(double x, double y) { this.X = x; this.Y = y; }
public double X, Y;
public bool Equals(Point2D obj) { ... }
public Point2D Add(Point2D obj) => new Point2D(this.X + obj.X, this.Y + obj.Y);
}
[Augments<Point2D>]
partial struct Point3D : IEquatable<Point3D>
{
public Point3D(double x, double y, double z) {
this.Point2D = new(x, y);
this.Z = z;
}
public double Z;
public bool Equals(Point3D obj) { ... }
}
// source generator emits
partial struct Point3D : IEquatable<Point2D>
{
public Point2D Point2D;
public bool Equals(Point2D obj) => this.Point2D.Equals(obj); // have to define interface implementing methods, for boxing purposes
public static implicit operator Point2D(Point3D obj) => obj.Point2D;
} |
Beta Was this translation helpful? Give feedback.
-
I'm curious if we can go a much simpler route. Specifically, have the language just automatically lift members of nested-structs upwards as long as they do not collide with real members in the name-spaces they are lifted to. So, in other words, instead of the above, you just write: struct Point2D : IEquatable<Point2D>
{
public Point2D(double x, double y) { this.X = x; this.Y = y; }
public double X, Y;
public bool Equals(Point2D obj) { ... }
public Point2D Add(Point2D obj) => new Point2D(this.X + obj.X, this.Y + obj.Y);
}
struct Point3D : IEquatable<Point3D>
{
public Point2D Point2d;
public Point3D(double x, double y, double z) { Point2d = new(x, y); this.Z = z; }
public double Z;
public bool Equals(Point3D obj) { ... }
} And Point3d automatically exposes Put another way, name lookup on Point3d would work by looking up in Point3d first, then inside the nested structs within. If doing this automatically was undesirable, it could be done through a keyword like struct Point3D : IEquatable<Point3D>
{
promote public Point2D Point2d; |
Beta Was this translation helpful? Give feedback.
-
I think it would be helpful to not only say what would be possible here but also the use cases why it would be valuable. For example the interface/ref stuff just doesn't seem that major. How often do you need to do this, and in those cases, would it be that bad to just say |
Beta Was this translation helpful? Give feedback.
-
but, just inherit the struct man |
Beta Was this translation helpful? Give feedback.
-
But in general what if you just use explicit layout, for example if I want to augment point2D in point3D just declare |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi I just ran across a use case for "promoted" fields (or even properties) the way that you envisioned them, which is separate from my struct augmentation idea. Specifically, imagine your typical business software with data entities; it's frequently necessary to reuse certain properties between multiple entities, but lack of multiple inheritance prevents us from doing so elegantly. I think "promotion" would be the next best thing: interface IOrganization
{
promoted LegalEntityDescriptor Descriptor { get; set; }
}
class Company : IOrganization
{
public string Name { get; set; } // if this is present, then the line below generates a compile error
public promoted LegalEntityDescriptor Descriptor { get; set; }
}
class Charity : IOrganization
{
public promoted LegalEntityDescriptor Descriptor { get; set; }
}
class LegalEntityDescriptor // or struct
{
public string Name { get; set; }
public string MailingAddress { get; set; }
} I think it makes sense to go with explicit approach here:
Thoughts? |
Beta Was this translation helpful? Give feedback.
-
On second thought, I'm actually now thinking both the "augmentation" and the "promotion" can be combined into one, sweeping improvement. class Person : EntityBase, IEquatable<Person>, promotes LegalEntityDescriptor
{
// ...
}
struct Point3D : IEquatable<Point3D>, promotes Point2D // for structs, this is the same as augments above
{
// ...
} In fact, classes can promote structs and vice versa, as needed. This solves (for most use cases) two very important and fundamental shortcomings of C# today: lack of multiple inheritance and lack of struct inheritance. It allows greater use of structs to reduce allocations, where desirable. |
Beta Was this translation helpful? Give feedback.
-
To anybody else who is interested in this: I did end up writing a source generator to imitate this behavior, and while it has a few downsides compared to a native language solution to allow "promotion"/"augmentation"/"mixins" - I decided to name it "composition" - it does make quite a few scenarios a lot easier. Supporting types[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
public class ComposedOfAttribute : Attribute
{
public ComposedOfAttribute(Type type)
{
this.Type = type;
}
public Type Type { get; }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class CompositionPartAttribute : Attribute
{
}
public interface IComposedOf<T>
{
T Base { get; }
} (BTW why aren't .cs files allowed to be attached??!!...) PS. This is the first ever analyzer/generator I wrote - any constructive criticism is very welcome. |
Beta Was this translation helpful? Give feedback.
-
I've vastly improved the source generator and created a Github repo and Nugets for it: https://github.com/TahirAhmadov/Composition |
Beta Was this translation helpful? Give feedback.
-
As discussed in #524, we allow "augmenting" structs, for the purposes of code reusability.
Open questions:
Rectangle
toPoint
in the above example. Should it be anexplicit
operator maybe, or no operator at all?abstract struct
s? If so, what are the rules? The only possible limitation can be inability to call the constructors, butdefault
can always be assigned to a local variable, for example. Or a blanket ban on using anywhere, except for "augmentation"?augments
, or just good old:
will do? The upside ofaugments
is that it's clear that this is not class-style inheritance; the downside is it's verbose, and it breaks from C# style - becomes more Java-like, which isn't necessarily a bad thing, but it's a change nonetheless.If anybody has questions, I will try to answer and update this post, as needed.
Beta Was this translation helpful? Give feedback.
All reactions