Porting a C# Class

Step-by-step walkthrough of porting a C# class to sharp-runtime.

The C# Original

We will port this simple C# class:

// C# original
using System;
using System.Collections.Generic;

namespace MyApp
{
    public class Player
    {
        public string Name { get; set; }
        public int Score { get; private set; }
        private List<string> achievements_ = new();

        public event EventHandler<ScoreChangedArgs> ScoreChanged;

        public Player(string name)
        {
            Name = name;
            Score = 0;
        }

        public void AddScore(int points)
        {
            Score += points;
            ScoreChanged?.Invoke(this, new ScoreChangedArgs(Score));
        }

        public void AddAchievement(string name)
        {
            achievements_.Add(name);
        }

        public override string ToString()
        {
            return $"{Name}: {Score} pts";
        }
    }

    public class ScoreChangedArgs : EventArgs
    {
        public int NewScore { get; }
        public ScoreChangedArgs(int score) { NewScore = score; }
    }
}

Step 1: Create the Header File

Map each C# element to its sharp-runtime equivalent:

// Player.hpp
#pragma once
#include <string>
#include <System/Object.hpp>
#include <System/EventHandler.hpp>
#include <System/EventArgs.hpp>
#include <System/Collections/Generic/List.hpp>
#include <SharpRuntime/Prop.hpp>

namespace MyApp {

class ScoreChangedArgs : public System::EventArgs {
public:
    explicit ScoreChangedArgs(int score) : newScore_(score) {}
    DGETTER(int, NewScore)
    GetTypeNameHPP();
private:
    int newScore_;
};

class Player : public System::Object {
public:
    explicit Player(const std::string& name);

    // Properties
    DDATA(std::string, Name)      // read-write
    DGETTER(int, Score)            // read-only from outside

    // Event
    System::EventHandler<ScoreChangedArgs> ScoreChanged;

    // Methods
    void AddScore(int points);
    void AddAchievement(const std::string& name);
    std::string ToString() const override;
    GetTypeNameHPP();

private:
    int score_ = 0;
    System::Collections::Generic::List<std::string> achievements_;
};

} // namespace MyApp

Step 2: Create the Implementation File

// Player.cpp
#include "Player.hpp"
#include <System/String.hpp>

namespace MyApp {

// ScoreChangedArgs
IGETTER(ScoreChangedArgs, int, NewScore, newScore_)
GetTypeNameCPP(ScoreChangedArgs, "MyApp.ScoreChangedArgs")

// Player
IDATA(Player, std::string, Name)
IGETTER(Player, int, Score, score_)
GetTypeNameCPP(Player, "MyApp.Player")

Player::Player(const std::string& name) : name_(name), score_(0) {}

void Player::AddScore(int points) {
    score_ += points;
    if (!ScoreChanged.Empty()) {
        ScoreChanged.Raise(this, ScoreChangedArgs(score_));
    }
}

void Player::AddAchievement(const std::string& name) {
    achievements_.Add(name);
}

std::string Player::ToString() const {
    return name_ + ": " + std::to_string(score_) + " pts";
}

} // namespace MyApp

Step 3: Usage

#include "Player.hpp"
#include <iostream>

int main() {
    MyApp::Player player("Alice");

    // Subscribe to event
    player.ScoreChanged += [](System::Object* sender, MyApp::ScoreChangedArgs args) {
        std::cout << "Score changed to: " << args.getNewScoreProperty() << "\n";
    };

    player.AddScore(50);   // prints: Score changed to: 50
    player.AddScore(25);   // prints: Score changed to: 75

    std::cout << player.ToString() << "\n"; // Alice: 75 pts
    return 0;
}

Key Differences Summary

C# constructsharp-runtime equivalent
public string Name { get; set; }DDATA(std::string, Name) + IDATA()
public int Score { get; private set; }DGETTER(int, Score) + IGETTER() + private field
new List<string>()System::Collections::Generic::List<std::string>
event EventHandler<T> E;System::EventHandler<T> E; (public)
E?.Invoke(this, args)if (!E.Empty()) E.Raise(this, args);
: EventArgs: public System::EventArgs
override string ToString()std::string ToString() const override
$"{name}: {score}"name_ + ": " + std::to_string(score_)