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.