Porting C# Code

Systematic guide to porting .NET code to sharp-runtime.

Type Mappings

Primitive Types

C# typesharp-runtime aliasUnderlying C++ type
intintcsint32_t
longlongcsint64_t
shortshortcsint16_t
bytebytecsuint8_t
sbytesbytecsint8_t
uintuintcsuint32_t
ulongulongcsuint64_t
ushortushortcsuint16_t
floatfloatfloat
doubledoubledouble
boolboolbool
charcharcschar16_t
stringstd::stringstd::string
objectSystem::Object*pointer

Include <SharpRuntime/SharpRuntimeHelper.hpp> for the aliases.

Collections

C# typesharp-runtime type
T[]std::vector<T>
List<T>System::Collections::Generic::List<T>
Dictionary<K,V>System::Collections::Generic::Dictionary<K,V>
HashSet<T>System::Collections::Generic::HashSet<T>
Queue<T>System::Collections::Generic::Queue<T>
Stack<T>System::Collections::Generic::Stack<T>

Memory Management

Most Critical Difference
C# has garbage collection. sharp-runtime does not. Every object must have an explicit owner. Failing to handle ownership leads to memory leaks or use-after-free bugs.
C# patternsharp-runtime equivalent
new MyClass()std::make_unique<MyClass>() (single owner)
Shared referencestd::make_shared<MyClass>()
using (var x = ...)Destructor runs at scope end (RAII)
x = nullptr.reset() or let scope end
x == nullptr == nullptr or !ptr

Properties

// C#
public int Width { get; set; }
public int Area => Width * Height;  // computed

// sharp-runtime
// In .hpp:
DDATA(int, Width)
DGETTER(int, Area)

// In .cpp:
IDATA(MyClass, int, Width)
IGETTER(MyClass, int, Area, width_ * height_)

Events

// C#
public event EventHandler<MyArgs> Changed;
Changed?.Invoke(this, new MyArgs());

// sharp-runtime
System::EventHandler<MyArgs> Changed;
if (!Changed.Empty()) Changed.Raise(this, MyArgs{});

String Interpolation

// C#
string msg = $"Hello {name}, you are {age} years old.";

// sharp-runtime (option 1: String::Format)
std::string msg = System::String::Format("Hello {0}, you are {1} years old.", name, age);

// sharp-runtime (option 2: std::string concatenation)
std::string msg = "Hello " + name + ", you are " + std::to_string(age) + " years old.";

Null Coalescing

// C#
string name = input ?? "default";
int val = n ?? 0;

// sharp-runtime
std::string name = input.empty() ? "default" : input;
int val = n.GetValueOrDefault(0); // for Nullable<int> n

foreach and LINQ

// C# foreach
foreach (var item in items) { ... }

// sharp-runtime (range-for on std::vector or List<T>)
for (const auto& item : items) { ... }

// C# LINQ — no direct equivalent
// Use std::algorithm or manual loops
var names = players.Where(p => p.Score > 0).Select(p => p.Name).ToList();

// sharp-runtime equivalent
std::vector<std::string> names;
for (const auto& p : players) {
    if (p.getScoreProperty() > 0)
        names.push_back(p.getNameProperty());
}

Async/Await

C# async/await has no direct equivalent in sharp-runtime. Options:

Interfaces

// C#
public interface IDrawable { void Draw(); }
public class Sprite : IDrawable { public void Draw() { ... } }

// sharp-runtime (pure abstract base class)
class IDrawable {
public:
    virtual ~IDrawable() = default;
    virtual void Draw() = 0;
};
class Sprite : public IDrawable {
public:
    void Draw() override { ... }
};

Exception Handling

// C# — works the same in sharp-runtime
try {
    doSomething();
} catch (System::ArgumentException& e) {
    std::cerr << e.what() << "\n";
} catch (System::Exception& e) {
    std::cerr << "Error: " << e.what() << "\n";
}

Note: In C++, catch by reference (&). In C#, catch by type without &.