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:

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::NowDateTime::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:

WhatC++ name
Backing fieldwidth_ (lowercase first letter + underscore)
Getter methodgetWidthProperty()
Setter methodsetWidthProperty(value)
Why not operator overloading or template magic?
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_) + ")";
}
}