Tangible Software Solutions

C++ and C# Comparison and Equivalents


Equivalents were produced with the Free Edition of C++ to C# Converter and the Free Edition of C# to C++ Converter.


C# C++
public abstract class AbstractClass
{
    protected abstract void AbstractMethod();
}
class AbstractClass
{
protected:
    virtual void AbstractMethod() = 0;
};

Sized Array

C++ C#
int myArray[2];

or:

int *myArray = new int[2];
int[] myArray = new int[2];

Access Array Element

C++ C#
x = myArray[0]; x = myArray[0];

Jagged Array

C++ C#
int **myArray = new int*[2]; int[][] myArray = new int[2][];

Rectangular Array

C++ C#
int myArray[2][3];

int[][] myArray = { new int[3], new int[3] };
C# C++
x = (FooType)myObject;
x = myObject as FooType;

Smart pointers:

x = std::static_pointer_cast<FooType>(myObject);
x = std::dynamic_pointer_cast<FooType>(myObject);

Raw pointers:

x = static_cast<FooType*>(myObject);
x = dynamic_cast<FooType*>(myObject);
C# C++
// List:
List<int> myList = new List<int>() {1, 2, 3};

// Dictionary:
Dictionary<string, int> myD = new Dictionary<string, int>() {
    {name1, 80},
    {name2, 85}
};
// vector:
std::vector<int> myList = {1, 2, 3};

// unordered_map:
std::unordered_map<std::wstring, int> myD = {
    {name1, 80},
    {name2, 85}
};

Vectors (C++) and Lists (C#)

C++ C#
#include <vector>

void Vector()
{
    std::vector<int> myList;
    myList.push_back(1);
    int i = 1;
    myList[0] = i;
    i = myList[0];
}
using System.Collections.Generic;

void Vector()
{
    List<int> myList = new List<int>();
    myList.Add(1);
    int i = 1;
    myList[0] = i;
    i = myList[0];
}

Maps (C++) and Dictionaries (C#)

(std::map and SortedDictionary are also very close equivalents).

C++ C#
#include <string>
#include <unordered_map>

void UnorderedMap()
{
    std::unordered_map<std::wstring, int> map;
    std::wstring s = L"test";
    map.emplace(s, 1);
    int i = map[s];
    i = map.size();
    bool b = map.empty();
    map.erase(s);
}
using System.Collections.Generic;

void UnorderedMap()
{
    Dictionary<string, int> map = new Dictionary<string, int>();
    string s = "test";
    map.Add(s, 1);
    int i = map[s];
    i = map.Count;
    bool b = map.Count == 0;
    map.Remove(s);
}

Local Constant

C++ C#
constexpr int myConst = 2; const int myConst = 2;

Class Constant

C++ C#
public:
static constexpr int myConst = 2;
public const int myConst = 2;

Local Variable

C++ C#
int myVar = 2; int myVar = 2;

Inferred Types

C++ C#
auto myVar = 2; var myVar = 2;

Static Field

C++ C#
public:
static inline int S = 0;
public static int S = 0;

Read-Only Field

C++ C#
public:
const int R = 2;
public readonly int R = 2;

A C# finalizer uses the '~' operator, but is not equivalent to a C++ destructor. The implementation of the 'Dispose' method in the IDisposable interface can be converted to a C++ destructor, but this is not exactly equivalent.

C# C++
class Foo: System.IDisposable
{
    public Foo() : this(0)   // call to other constructor
    {
    }
    public Foo(int i)
    {
    }

    public void Dispose()
    {
    }

    ~Foo()   // 'finalizer' method
    {
    }
}
class Foo
{
public:
    Foo() : Foo(0)   // call to other constructor
    {
    }
    Foo(int i)
    {
    }

    ~Foo()   // destructor
    {
    }

private:
// C# TO C++ CONVERTER WARNING: There is no equivalent in C++ to finalizer methods:
    void finalize()
    {
    }
};
C++ C#
int/signed int
long/long int/signed long/signed long int

long long/long long int
signed long long/signed long long int

short/short int
signed short/signed short int

unsigned/unsigned int
unsigned long/unsigned long int

unsigned short/unsigned short int
unsigned long long/unsigned long long int

bool
char/wchar_t
signed char
unsigned char
float
double/long double
std::wstring (no C++ language built-in type)
std::any (C++17) or void * (no C++ language built-in type)
No C++ language or standard library equivalent
int
int (may convert to 'long' on some systems)

long
long

short
short

uint
uint (may convert to 'ulong' on some systems)

ushort
ulong

bool
char
sbyte
byte
float
double
string
object
decimal

A C++ function pointer corresponds to a C# delegate.

C++ C#
// C++11 syntax:
using MyDelegate = std::function<void (int i)>;

// before C++11:
using MyDelegate = void (*)(int i);
public delegate void MyDelegate(int i);
C# C++
// implicitly-typed:
public enum FooEnumOne
{
    ValueOne,
    ValueTwo
}

// explicitly-typed:
public enum FooEnumTwo : int
{
    ValueOne,
    ValueTwo
}
// implicitly-typed:
enum class FooEnumOne
{
    ValueOne,
    ValueTwo
};

// explicitly-typed:
enum class FooEnumTwo : int
{
    ValueOne,
    ValueTwo
};

C++ (other than C++/CLI) does not have any syntax which is specific to events. C# has the event keyword which allows you to declare an event to which you can easily subscribe or unsubscribe multiple listeners in addition to letting all the listeners know when the event occurs. To do this in C++, you need to replace the event's delegate type with a std::function function pointer and replace the event declaration with a new field declaration using our generic 'TangibleEvent' helper class. The 'TangibleEvent' class takes care of two different listener collections and has methods for adding or removing an event subscription. Two different collections are necessary since C# allows subscribing to an event by specifying a method name or delegate instance(named listener) or subscribing by specifying a lambda.

Here's a simple example:

C# C++
public class EventSource
{
    public delegate void FooDelegate();
    public event FooDelegate FooEvent;

    private void RaiseFooEvent()
    {
        FooEvent();
    }
}

public class EventClient
{
    public void SubscribeAndUnsubscribe()
    {
        EventSource t = new EventSource();
        // subscribe via method name:
        t.FooEvent += HandleFooEvent;
        // unsubscribe via method name:
        t.FooEvent -= HandleFooEvent;

        // subscribe via lambda:
        t.FooEvent += () =>
        {
            HandleFooEvent();
        };
    }

    private void HandleFooEvent()
    {
    }
}
#include "tangible_event.h"

class EventSource
{
    using FooDelegate = std::function<void ()>;
public:
    TangibleEvent<FooDelegate> *FooEvent = new TangibleEvent<FooDelegate>();

    void RaiseFooEvent()
    {
        // invoke all listeners:
        for (auto listener : FooEvent->listeners())
        {
            listener();
        }
    }
};

class EventClient
{
public:
    void SubscribeAndUnsubscribe()
    {
        EventSource *t = new EventSource();
        // subscribe via method name:
        t->FooEvent->addListener(L"HandleFooEvent", [&] () {HandleFooEvent();});
        // unsubscribe via method name:
        t->FooEvent->removeListener(L"HandleFooEvent");

        // subscribe via lambda:
        t->FooEvent->addListener([&] ()
        {
            HandleFooEvent();
        });
    }

private:
    void HandleFooEvent()
    {
    }
};

// "tangible_event.h" follows:
// ----------------------------------------------------------------------------------------
// Copyright © 2007 - 2024 Tangible Software Solutions, Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// This class is used to convert C# events to C++.
// ----------------------------------------------------------------------------------------
#include <string>
#include <unordered_map>
#include <vector>
#include <functional>

template<typename T>
class TangibleEvent final
{
private:
    std::unordered_map<std::wstring, T> namedListeners;
    std::vector<T> anonymousListeners;

public:
    void addListener(const std::wstring &methodName, T namedEventHandlerMethod)
    {
        if (namedListeners.find(methodName) == namedListeners.end())
            namedListeners[methodName] = namedEventHandlerMethod;
    }

    void addListener(T unnamedEventHandlerMethod)
    {
        anonymousListeners.push_back(unnamedEventHandlerMethod);
    }

    void removeListener(const std::wstring &methodName)
    {
        if (namedListeners.find(methodName) != namedListeners.end())
            namedListeners.erase(methodName);
    }

    std::vector<T> listeners()
    {
        std::vector<T> allListeners;
        for (auto listener : namedListeners)
        {
            allListeners.push_back(listener.second);
        }
        allListeners.insert(allListeners.end(), anonymousListeners.begin(), anonymousListeners.end());
        return allListeners;
    }
};

C++ doesn't have extension methods, so a C# extension method is just converted to an ordinary C++ static method (calls to the method have to be adjusted to static calls using the class name).

C# C++
public static class ContainsExtension
{
    public static void ExtensionMethod(this string myParam)
    {
        // ...
    }
}
class TestClass
{
    void TestMethod()
    {
        string s;
        s.ExtensionMethod();
    }
}
#include <string>

class ContainsExtension final
{
public:
    static void ExtensionMethod(const std::string &myParam)
    {
        // ...
    }
};
class TestClass
{
private:
    void TestMethod()
    {
        std::string s;
        ContainsExtension::ExtensionMethod(s);
    }
};
C++ C#
for (std::string s : StringList)
{
    ...
}
foreach (string s in StringList)
{
    ...
}

C# generics and C++ templates are implemented in totally different ways — C# generics is a runtime feature, while C++ templates result in separate classes created at compile-time, but you can still often achieve the same result by converting one to the other.

Creating a List

C# C++
List<int> myVar = new List<int>(); std::vector<int> myVar;

Creating a Dictionary

C# C++
Dictionary<string, int> myVar = new Dictionary<string, int>(); std::unordered_map<std::string, int> myVar;

Defining a Generic Class

C# C++
public class GenericClass<T>
{
}
template<typename T>
class GenericClass
{
};

Defining a Generic Class with a Constraint

C# C++
public class GenericClass<T> where T: SomeBase
{
}
#include <type_traits>

template<typename T>
class GenericClass
{
    static_assert(std::is_base_of<SomeBase, T>::value, "T must inherit from SomeBase");
};

Defining a Generic Class with a 'new' Constraint

C# C++
public class GenericClass<T> where T: new()
{
}
#include <type_traits>

template<typename T>
class GenericClass
{
    static_assert(std::is_default_constructible<T>::value, "T requires default-constructible elements");
};

Defining a Generic Method

C# C++
public int Compare<T>(T param1, T param2)
{
}
public:
template<typename T>
int Compare(T param1, T param2)
{
}

Defining a Generic Method with a Constraint

C# C++
public void Swap<T>(ref T l, ref T r) where T: class
{
}
#include <type_traits>

public:
template<typename T>
void Swap(T &l, T &r)
{
    static_assert(std::is_class<T>::value, "T must be a class");
}
C++ C#
public:
int &operator [](int index)
{
    return field[index];
}

*for a read-only indexer, return an int instead of an int reference (remove the '&').
public int this[int index]
{
    get
    {
        return field[index];
    }
    set
    {
        field[index] = value;
    }
}
C++ C#
class DerivedClass : public BaseClass
{
public:
    virtual void MethodOverrideOne() override
    {
    }
    virtual void MethodOverrideTwo() final override
    {
    }
};
public class DerivedClass : BaseClass
{
    public override void MethodOverrideOne()
    {
    }
    public override sealed void MethodOverrideTwo()
    {
    }
}

C++ doesn't have a separate 'interface' concept, but this is done just as easily in C++ using a class with only 'pure virtual methods'.

C# C++
public interface IFoo
{
    void Method();
}
class IFoo
{
public:
    // pure virtual method:
    virtual void Method() = 0;
};
C# C++
myVar = (string text) => foo(text); myVar = [&] (const std::string &text)
{
    return foo(text);
};
C# C++
lock (x)
{
    ...
}
#include <mutex>

...

{
    std::scoped_lock<std::mutex> lock(x);
    ......
}

C# operator overloading is similar to C++ operator overloading, except that C# operator overloads are static methods while C++ operator overloads are instance methods. During conversion by C++ to C# Converter, references to 'this' within the method are converted to the added first parameter.

C++ C#
class SomeType
{
private:
    int IntValue = 0;

public:
    int operator + (const SomeType &Y)
    {
        return this->IntValue + Y.IntValue;
    }
};
public class SomeType
{
    private int IntValue;

    public static int operator + (SomeType impliedObject, SomeType Y)
    {
        return impliedObject.IntValue + Y.IntValue;
    }
}

The syntax for optional parameters is identical in these two languages. In C++, it is more common to call them 'default parameters'.

C++ C#
void TestOptional(int x = 0)
{
    ...
}
void TestOptional(int x = 0)
{
    ...
}

Even though C# allows pointers in 'unsafe' mode, this is not a good practice for converting C++ code to C#. Below is the conversion to typical C#.

C++ C#

Raw pointers:

Foo *f = new Foo();

Smart pointers:

std::shared_ptr<Foo> f = std::make_shared<Foo>();
Foo f = new Foo();
C++ C#
// #define replacement:
#define FOO 5

// #define macro:
#define MAX_TEST(y, z) y > z ? y : z
...
#if defined(FOO)
int i = FOO;
#elif defined(MAX_TEST)
int i = MAX_TEST(2, 3);
#endif
...
// variadic macro:
#define BAR(...) printf(__VA_ARGS__)
BAR("print this");
#define FOO
#define MAX_TEST
...
#if FOO
int i = DefineConstants.FOO;
#elif MAX_TEST
int j = 2 > 3 ? 2 : 3;
#endif
...
Console.Write("print this");
...
internal static class DefineConstants
{
    public const int FOO = 5;
}

C++ does not have properties, so you must use get/set methods instead:

C# C++
public int IntProperty
{
    get
    {
        return intField;
    }
    set
    {
        intField = value;
    }
}
public:
int getIntProperty() const
{
    return intField;
}
void setIntProperty(int value)
{
    intField = value;
}
C# C++
public void method(ref int valueTypeParam, ref Foo refTypeParam)
{
    ...
}

Smart pointers:

#include <memory>

public:
void method(int &valueTypeParam, std::shared_ptr<Foo> &refTypeParam)
{
//...
}

Raw pointers:

public:
void method(int &valueTypeParam, Foo *&refTypeParam)
{
    ...
}

The C++ equivalent to a C# static constructor uses a private nested class.

C# C++
class Foo
{
    public static int field;

    static Foo()
    {
        field = 1;
    }
}
class Foo
{
public:
    static inline int field = 0;

private:
    class StaticConstructor
    {
    public:
        StaticConstructor()
        {
            field = 1;
        }
    };

private:
    static inline Foo::StaticConstructor staticConstructor;
};

C++ and C# 'switch' are mostly equivalent, except that C++ 'switch' allows fall-through from a non-empty 'case' to the next 'case' or 'default'. In C#, a jump statement (break, return, throw, or goto) must be reached on every path through the 'case' logic, so a 'goto' statement is required to produce a fall-through from a non-empty 'case'.

C++ C#
// fall-through from a non-empty 'case':
switch (someCondition)
{
    case 1:
        code;
    case 2:
        code;
    default:
        code;
}
// fall-through from a non-empty 'case':
switch (someCondition)
{
    case 1:
        code;
        goto case 2;
    case 2:
        code;
        goto default;
    default:
        code;
        break;
}

In C++17, the std::any type offers a good equivalent to System.Object.

C# C++ (C++17)
object o = 1;
int i = (int)o;
Type t = o.GetType();
#include <any>
#include <typeinfo>

std::any o = 1;
int i = std::any_cast<int>(o);
std::type_info t = o.type();

There is no direct equivalent in C++ to the C# 'is' operator, but you can replicate the behavior by testing the result of a 'dynamic_cast':

C# C++
bool b = f is Foo;
Smart pointers:

bool b = std::dynamic_pointer_cast<Foo>(f) != nullptr;

Raw pointers:

bool b = dynamic_cast<Foo*>(f) != nullptr;

C++ 'typeid' is only equivalent to C# 'typeof' for template type parameters:

C# C++
private void method<T>()
{
    Type t = typeof(T);
}
#include <typeinfo>

template<typename T>
void method()
{
    std::type_info t = typeid(T);
}
C# C++
using Foo;
using static Foo.Bar;

// namespace alias:
using foo = SomeNamespace;
// type alias:
using bar = SomeNamespace.SomeType;
using namespace Foo;
*no equivalent*

// namespace alias:
namespace foo = SomeNamespace;
// type alias:
using bar = SomeNamespace.SomeType;
C# C++
using (Foo f = new Foo())
{
    f.method();
}
{
    Foo *f = new Foo();
    f->method();
    delete f;
}

or, if 'Foo' is designed for RAII:

{
    Foo f;
    f.method();
}
C# C++
// verbatim string:
string s = @"multiline
    verbatim
    string";

// C#11 raw string literal:
string s = """
multiline
    verbatim
    string
""";
std::string s = R"(multiline
    verbatim
    string)";

Copyright © 2004 – 2024 Tangible Software Solutions, Inc.