Property Macros
The Prop.hpp macro system that implements .NET-style properties in C++23.
include/SharpRuntime/Prop.hpp
Motivation
C++ has no built-in property syntax. C# properties like obj.Width (which call
getter/setter methods transparently) become explicit method calls in C++. The sharp-runtime
coding convention is to expose properties as getXxxProperty() / setXxxProperty()
methods, and the Prop.hpp macros generate the required boilerplate.
This naming convention is enforced by CLAUDE.md — it is non-negotiable for all
new code in the project.
Source Macros (in .hpp files)
DDATA — Declare data member + getter + setter
DDATA(type, Name)
Expands to:
- A
privatedata member:type name_; - A public getter:
type getNameProperty() const; - A public setter:
void setNameProperty(type value);
class Rectangle : public System::Object {
public:
DDATA(int, Width)
DDATA(int, Height)
GetTypeNameHPP();
};
DGETTER — Declare getter only (read-only property)
DGETTER(type, Name)
Expands to a public getter declaration only. No setter is generated. Use for computed or read-only properties.
class Rectangle : public System::Object {
public:
DDATA(int, Width)
DDATA(int, Height)
DGETTER(int, Area) // computed: Width * Height
GetTypeNameHPP();
};
DGETTERSTATIC — Declare static getter
DGETTERSTATIC(type, Name)
Declares a static getter method: static type getNameProperty();
Used for static properties like DateTime::Now → DateTime::getNowProperty().
Implementation Macros (in .cpp files)
IDATA — Implement getter + setter
IDATA(ClassName, type, Name)
Provides default getter (returns the backing field) and setter (assigns to the backing field). Most properties use this — it is the standard implementation for simple value properties.
// Rectangle.cpp
IDATA(Rectangle, int, Width)
IDATA(Rectangle, int, Height)
Expands to approximately:
int Rectangle::getWidthProperty() const { return width_; }
void Rectangle::setWidthProperty(int value) { width_ = value; }
int Rectangle::getHeightProperty() const { return height_; }
void Rectangle::setHeightProperty(int value) { height_ = value; }
IGETTER — Implement getter only
IGETTER(ClassName, type, Name, expression)
Provides the getter with a custom expression. Use for computed properties.
IGETTER(Rectangle, int, Area, width_ * height_)
IGETTERSTATIC — Implement static getter
IGETTERSTATIC(ClassName, type, Name, expression)
Implements a static property getter.
GetTypeName Macros
Two macros handle the pure virtual GetTypeName() from System::Object:
GetTypeNameHPP()
GetTypeNameHPP()
Placed in the class body in the .hpp file. Expands to:
std::string GetTypeName() const override;
GetTypeNameCPP(CLASS, NAME)
GetTypeNameCPP(Rectangle, "System.Drawing.Rectangle")
Placed in the .cpp file. Expands to:
std::string Rectangle::GetTypeName() const { return "System.Drawing.Rectangle"; }
Naming Convention
The naming rule for a property named Width:
| What | C++ name |
|---|---|
| Backing field | width_ (lowercase first letter + underscore) |
| Getter method | getWidthProperty() |
| Setter method | setWidthProperty(value) |
include/SharpRuntime/Experimental/Property.hpp contains an experimental
std::function-based property wrapper, but it is NOT used in the main
sharp-runtime codebase. The macro approach was chosen for simplicity, zero overhead,
and debuggability.
Example: Complete Class
// --- Vector2D.hpp ---
#pragma once
#include <SharpRuntime/Prop.hpp>
#include <System/Object.hpp>
namespace MyLib {
class Vector2D : public System::Object {
public:
DDATA(float, X)
DDATA(float, Y)
DGETTER(float, Length)
GetTypeNameHPP();
std::string ToString() const override;
};
}
// --- Vector2D.cpp ---
#include "Vector2D.hpp"
#include <cmath>
namespace MyLib {
IDATA(Vector2D, float, X)
IDATA(Vector2D, float, Y)
IGETTER(Vector2D, float, Length, std::sqrt(x_ * x_ + y_ * y_))
GetTypeNameCPP(Vector2D, "MyLib.Vector2D")
std::string Vector2D::ToString() const {
return "(" + std::to_string(x_) + ", " + std::to_string(y_) + ")";
}
}