2.9.1 Porter Limitations and Bugs

Last modified by Tilal Ahmad on 2018/10/03 07:12

Contents Summary

Porter Limitations and Bugs

This Page Contains Porter limitations and bugs.

Type conversion when attaching an event handler is not supported

Attaching a delegate object returned by implicit or explicit type conversion operation to an event object is not supported. Only a delegate object of corresponding type, which does not require implicit conversion, can appear on the right side of += operator of an event.

So the following code is not supported by the Porter - it will be ported to syntactically incorrect C++ code:

namespace ns
{
   public delegate void D();

   public class A
    {
       public event D e;

       public void M(){}

       public void Do()
        {
            e += M;
        }
    }
}

while the following equivalent code is supported by Porter:

namespace ns
{
   public delegate void D();

   public class A
    {
       public event D e;

       public void M(){}

       public void Do()
        {
            D d = new D(M);
            e += d;

           // Shorter form without intermediate varibale
           e += new D(M);
        }
     }
}

Static events are not supported

Porter does not support static events. static modifier in the declaration of event member is ignored and such event is interpreted and translated as if it was an instance event.

Extension methods are not supported

Porter does not support extension methods. Method argument's this modifer is ignored by Porter and the method is ported as if its first argument had no this modifer.

'new' modifier in method declaration is not supported

Porter does not support new modifier in method declaration. Porter ignores new modifier in method declaration and translates the method declaration as if it had no new modifier.

Type parameter constraints in delegate declaration are not supported

Porter ignores type parameter constraints in delegate declaration.

So the following C# code that declares two generic delegates one of which contains specification of type constraints

namespace ns
{
   public delegate TOut D1<TIn, TOut>(TIn value);
   public delegate TOut D2<TIn, TOut>(TIn value) where TIn : ICloneable where TOut : IConvertible;
}

will be ported into two basically identical C++ declarations

namespace ns
{
    template <typename TIn, typename TOut> using D1 = System::MulticastDelegate<TOut(TIn)>;
    template <typename TIn, typename TOut> using D2 = System::MulticastDelegate<TOut(TIn)>;
}

C++ anonymous functions capturing outer variables behave differently than those in C#

There is an important difference between behavior of C# code and corresponding C++ code generated by Porter with regard to anonymous functions. A C# anonymous function that captures outer variables them for as long as the anonymous function object lives, which guarntees that the lifetime of the objects pointed to by these variables will not end at least until the the moment anonymous function object's lifetime ends. This is not the case in generated C++ code. A C++ anonymous function that results from translating a C# anonymous function captures outer variables by reference and does not influence their lifetimes.

Let us consider the following C# code, which executes without any problems

namespace ns
{
class Program
{
   class Foo
    {
       public void Bar(string s) { }
    }

   public static Action<string> Create()
    {
       Foo foo = new Foo();
      return s => foo.Bar(s);
    }

   public static void Main(string[] args)
    {
        Action<string> a = Create();
        a("hello");
    }
}
}

now let us translate it to C++ using Porter

namespace ns {

class Program : public System::Object
{
   class Foo : public System::Object
    {
   public:
       void Bar(System::String s);
    };
public:

   static System::Action<System::String> Create()
    {
        System::SharedPtr<Program::Foo> foo = System::MakeObject<Program::Foo>();

       // CSPORTCPP: [WARNING] Using local variables. Make sure that local function ptr does not leave the current scope.
       std::function<void(System::String s)> _local_func_0 = [&foo](System::String s)
        {
            foo->Bar(s);
        };

       return _local_func_0;
    }

   static void Main(System::ArrayPtr<System::String> args)
    {
        System::Action<System::String> a(Create());
        a(u"hello");
    }
};

An application compiled from this C++ code most probably will crash when executed, because variable refereced by foo in the body of anonymous function will have already been destroyed by the moment when the function is executed in method Main().

Porter will insert a reminder comment just before the definition of anonymous function in C++ code.

Invocation of overloaded method defined in base class through 'this' reference is translated incorrectly

Let us consider a class hierarchy composed by two classes Base and Derived where Derived is derived from Base. Let class Base contain a public method M() and let class Derived contain a public method M(int). Thus effectively we have class Derived containing two overloaded methods - one inherited from Base and one difined in Derived itself. For simplicity reasons let both methods Base.M() and Derived.M(int) have void as return type and empty bodies. And let Derived contain another method F(), which invokes inherited method A.M() using this reference. Here is the C# code that defines described hierarchy

namespace ns
{
   class Base
    {
       public void M() { }
    }

   class Derived : Base
    {
       public void M(int a) { }
       public void F()
        {
           // Invocation of M() defined in Base and overloaded in Derived using 'this' reference.
           this.M();
        }
    }
}

Given specified class system Porter will translate method F()'s body into syntactically incorrect C++ code. Here's how method F() would be translated into C++

// NOTE: code below is syntactically incorrect and won't compile
void Derived::F()
{
   // Invocation of M() defined in Base and overloaded in Derived using 'this' reference.
   System::StaticCast<Base>(this)->M();
}

Anonymous object creation expression is not supported

Porter does not support anonymous object creation expression and will output an error message when one is encountered in the input C# source code. Thus Porter will generate an error message when encounters following line of in C# code

var v = new { Amount = 108, Message = "Hello" };

Query expressions (LINQ) are not supported

Porter does not support LINQ expressions. Porter generates an error message when encounters a LINQ expression in C# code.

Variant and covariant type parameters are translated as invariant type parameters

Variance annotation - keywords in and out in variant type parameters lists - are ignored by Porter. All covariant and contravariant type parameters of an interface or a delegate encountered in C# code by Porter are interpreted and translated as invariant type parameters.

There is a limitation imposed on switch statement with string as governing type

If string is the governing type of switch expression in C# switch statement, such switch statement is translated into equivallent system of nested if-else statements in C++, if default Porter configuration is applied. The number of levels in resulting system of nested if-else statements is equal to the number of switch sections in a switch statement in C# code. Thus the following dummy C# switch statement

string s = "two";
switch (s)
{
   case "one": break;
   case "two": break;
   case "three": break;
   default: break;
}

will be translated into following C++ code

System::String s = u"two";
const System::String switch_value_0 = s;

if (switch_value_0 == u"one")
{
}
else if (switch_value_0 == u"two")
{
}
else if (switch_value_0 == u"three")
{
}
else if (true)
{
}

The problem arises when the number of switch sections in a C# switch statement is greater than 127. Because some C++ compilers support no more than 127 levels of nesting in a system of if-else statements, the generated C++ code may not compile. To overcome this limitation one may set Porter's option alternative_string_switch. This will force porter to translate C# switch statement into a set of non-nested simple C++ if statements all wrappend in a do-while loop. Thus with option alternative_string_switch set Porter will translate the sample C# switch statement into the following C++ code

System::String s = u"two";
{
   const System::String switch_value_0 = s;
   do {
       if (switch_value_0 == u"one")
        {
           break;
        }
       if (switch_value_0 == u"two")
        {
           break;
        }
       if (switch_value_0 == u"three")
        {
           break;
        }
       if (true)
        {
           break;
        }
    } while (false);
}

The second approach is free from the limitations of the first, but introduces its own. For examlpe if in input C# code switch statement is located in the body of loop statement and one or more switch sections have continue statement in their bodies, Porter will fail to translate this code into semantically equivalent C++ code.

yield statement is not supported

Porter does not support yield statement and will generate error if encounters one in C# code.

Translation of C# 'this' reference may not preserve semantics

Sometimes when translating C# this-reference, Porter will translate it into C++ this-pointer wrapped into System::SharedPtr<> shared pointer. This may be undesirable behavior, for example when an object is allocated on stack or there are no other shared references pointing to the object. As a result in such situations when SharedPtr<> wrapping the this-pointer goes out of scope, it will destroy the object, which may not be what is expected.

To force Porter not to wrap this-pointer in generated C++ code with SharedPtr<> object, one can use auto_ctor_self_reference Porter option or CppCtroSelfReference attribute for constructors or CppSelfReference attribute for normal methods.

Translation of invocation of virtual method in a class constructor may not preserve semantics

Porter translates invocation of virtual method in a C# class' constructor into invocation of virtual method in a C++ class' consructor without emulating rules applied in C# to resolve virtual method version to be invoked. This may result in a run-time difference between behavior of a C# class hierarchy and corresponding C++ class hierarchy generated by Porter. In the following example at the runtime during instantiation of class B in method Main() method B.f() will be invoked in the constructor of class A:

namespace ns
{
   class A
    {
       public A()
        {
            f();
        }

       public virtual void f()
        {}
    }

   class B : A
    {
       public override void f()
        {}
    }

   class App
    {
       public static int Main(string[] args)
        {
            B b = new en.B();
           return 0;
        }
    }
}

but in corresponding C++ code generated by Porter, method A::f() will be invoked in the constructor of class A:

namespace ns {

class A : public System::Object
{
public:
    A()
    {
        f();
    };

   virtual void f() {};
};

class B : public A
{
public:
   virtual void f() {};
};

class App : public System::Object
{
public:
   static int32_t Main(System::ArrayPtr<System::String> args)
    {
        System::SharedPtr<B> b = System::MakeObject<en::B>();
       return 0;
    };
};

}

Translation of static members' initializers may introduce semantic difference between original C# code and generated C++ code

Static members of a C# class are translated into static members of a C++ class. Because in C# language static member initialization order is well defined and in C++ it is not, a difference between how corresponding static members of original C# class and generated C++ class may be introduced during translation if intializers of a one or more static members refer other static members in other classes. In order to preserve static members intialization order, deferred_init Porter option may be used. Another alternative is translating problematic members as singletons using CppPortAsSingleton attribute.

Incomplete types are translated into stubs that won't compile

When Porter cannot completely resolve a C# type to generate complete definition of corresponding C++ type, it will insert a UnknownType stub in C++ code instead, which will intentionally make C++ code syntactically incorrect.

Class' method named Type() in C# code may lead to name collisions in generated C++ code

Porter includes following static method in every generated C++ class

static const System::TypeInfo& Type();

This may lead to name collisions when translating C# classes that have method named Type.

A C# class that inherits an interfece twice is translated into C++ class cannot be instantiated with System::MakeObject<> factory

A C# class that inherits an interface twice - once directly and once through a base class - will be ported into a C++ class that cannot be instantiated with System::MakeObject factory.
 Let's consider the following C# class hierarchy

namespace ns
{
   interface I
    {
       void M();
    }

   class A : I
    {
       public void M() { }
    }

   class B : A, I
    {
       public new void M() { }
    }
}

when passed to Porter, it will be translated into the following C++ class hierarchy

namespace ns {

class I : public System::Object
{
public:
   virtual void M() = 0;
};

class A : public virtual I
{
public:
   void M() {};
};

class B : public A, public I
{
public:
   void M() {};
};
}

Here class B inhertis interface I twice - once virtually and once non-virtually - which makes following instantiation statement to trigger compilation errors

System::SharedPtr<B> b = System::MakeObject<B>();

Index-based collection initialization list is not supported.

Porter does not support index-based collection initialization lists. When Porter encounters index-based collection initialization list, it will ouput an error message. Thus the following C# code will not be translated

var numbers = new Dictionary<int, string> {
    [1] = "one",
    [2] = "two",
    [3] = "three"
};

while the following equivallent C# code will be translated successfully

var numbers = new Dictionary<int, string> {
    { 1, "one" },
    { 2, "two" },
    { 3, "three" }
};

C# finally blocks containing code that may throw exceptions are translated in unsafe C++ code that may throw exceptions from destructor

Porter translates C# code in finally block into equivalent C++ code, which is put into a destructor of a special service object. This means that in code in C# finally block may throws an execpion, this same excetpion may potentially be thrown in destructor of a service object in generated C++ code, which can be disasterous for the C++ application. Thus the following C# code will be translated into unsafe C++ code and should be rewritten so that its finally block does not throw before passing the code to Porter for translation

// NOTE: following C# code is an example of code that should NOT be passed to Porter.
try
{
    A a = new A();
}
finally
{
   // Following line is bad because it will be translated into C++ code
   // that throws from destructor.
   throw new NullReferenceException();
}

C# class members marked with 'internal' access modifier are translated into public members

Because in C++ there is no analog of C#'s internal access modifier, internal members of C# classes are translated as public. Thus the following C# class

class A
{
   internal int a = 1;
   internal void F() { }
}

will be translated into following C++ class

class A : public System::Object
{
public:
    int32_t a;
   void F();
    A();
};

C# class' 'internal' access modifier is not supported

'internal' access modifier applied to C# class is ignored by Porter.

Binary arithmetic operators with one operand of 'Enum' type and the other operand of 'string' type are not supported

Porter does not support binary arithmetic operators with one operand of Enum type and the other of string type. Such operators will be translated into C++ code that won't link.
 Thus, the following C# code will be translated into C++ code that will fail to link

namespace ns
{
enum E { one = 1, two = 2, three = 3 };

class App
{
   public static int Main(string[] args)
    {
       string s = E.one + "hello";

       return 0;
    }
}
}

In generated C++ code there is not difference between null-reference string and empty string.

A C# null-reference of type string is always translated into an instance of System::String class that behaves as an empty string, rather than as a null-reference. In C# when a method is invoked on a null String object reference, a NullReferenceException is thrown. Correspoding C++ code does not throw.

Thus the following sample C# method when executed will trigger a NullReferenceException exception

void Foo()
{
   string s = null;
    s.IndexOf('a');
}

when translated it will turn into following C++ method

void Bar::Foo()
{
    System::String s;
    s.IndexOf('s');
}

which does not trigger any exceptions.

Translation of locking on a string object is not supported

Porter does not support lock statement that accepts a string object. Porter will traslate such statement into syntactically incorrect C++ code.

Inheriting interface implementation is not supproted by default

A concrete C# class that inherits an interface and does not implement one or more of the methods declared in it
 but instead inherits their implementation from a base class, will be translated into an abstract C++ class by default.
 Let us consider following C# class hierarchy

namespace ns
{
   interface I
    {
       void M();
    }

   class A : I
    {
       public void M() { }
    }

   class B : A, I
    {
    }
}

Here class B is concrete. If passed to Porter, this hierarchy will be translated into following C++ class hierarchy

namespace ns{
class I : public System::Object
{
public:
   virtual void M() = 0;
};

class A : public virtual I
{
public:
   void M() {};
};

class B : public A, public I
{
};
}

Here class B is abstract and cannot be instantiated.

Porter option allow_interface_members_base_class_impl inforces emulation of C#-like behavior of the classes in described situation.

Boxing and unboxing of structures is not supported by default

By default, Porter will translate boxing and unboxing of a structure into syntactically incorrect C++ code. In order to enable boxing and unboxing of a structure, the corresponding structure type must be marked with CsToCppPorter.CppAllowBoxing attribute and must provide implementation of method bool Equals(S) that compares two instances of a structure type and returns true if they are equal.
 Thus the following C# code defines a strucure that satisfies the requirements, which makes its generaed C++ counterpart boxable.

namespace ns
{
[CsToCppPorter.CppAllowBoxing]
struct S
{
   public bool Equals(S s)
    {
        return a == s.a;
    }

   public int a;
}
class App
{
   public static void Main(string[] args)
    {
        S s = new S();
       object o = s;
        s = (S)o;
    }
}
}

Porter translates embedded resources only from one project of a C# solution being ported

If a C# solution consists of a single project that has embedded resources, the resources will be translated and embedded in generated C++ project correctly. However, if a C# solution consists of multiple projects, two or more of which have embedded resources, Porter will translate resources from only one of them. Resources from the rest of the projects will be ignored.

Method Object.MemberwiseClone() may behave differently in C# code and generated C++ code

In C# method MemberwiseClone() performs shallow copy of current object regardless of the class in the context of which the method is invoked. Thus in the following example method MemberwiseClone() called in method A.MA1() will return a reference to an instance of class B, despite the fact it is invoked in a method of class A

namespace ns
{
   abstract class A
    {
       public void MA1()
        {
            Object o = MemberwiseClone();
        }

       abstract public void MA2();

       public int m_a = 0;
    }

   class B : A
    {
       // Dummy implementation of abstract method inherited from A.
       public override void MA2() { }

       public int m_b = 1;
    }

   class App
    {
       public static int Main(string[] args)
        {
            A b = new B();
            b.MA1();

           return 0;
        }
    }
}

If this code is passed to Porter, it will generate C++ code that behaves differently

namespace ns {

class ABSTRACT A : public System::Object
{
public:
    int32_t m_a;

   void MA1()
    {
        System::SharedPtr<System::Object> o = System::MemberwiseClone(this);
    }

   virtual void MA2() = 0;
    A() {};
};

class B : public A
{
public:
    int32_t m_b;
   virtual void MA2(){};
    B() : m_b(1) {};
};

class App : public System::Object
{
public:

   static int32_t Main(System::ArrayPtr<System::String> args)
    {
        System::SharedPtr<A> b = System::MakeObject<B>();
        b->MA1();
    }};
}

This C++ code is syntactically incorrect, because here method MemberwiseClone() invoked in method A::MA1() will try to return an instantiate and return an instance of class A (not B as it is in C#), but will fail because class A is abstract.

Porter option polymorphic_memberwiseclone will enforce C#-like behavior of method MemberwiseClone().

Compilers may generate warnings when processing translated C++ code

C++ code generated by Porter may not meet all the strictest requirements imposed by supported compilers, which may lead to warnings generated by compilers when processing the code.

Wrong instances of const methods can be called

If library declares some virtual method with 'const' qualifier and C# code doesn't have CppConstMethod attribute applied to implementation of this method, incorrect version of this method will be called. If the method is declared as abstract, the class becomes abstract, too.

Unions are not supported.

Unions declared using 'StructLayout(LayoutKind.Explicit)' attribute are not supported.

CppForceDynamicCastFromTypeParam attribute doesn't work for generic types arguments

CppForceDynamicCastFromTypeParam attribute works for generic methods type arguments, but not for generic types arguments.

NUnit's Assert.Catch() method support is limited

Porter provides limited support of Assert.Catch() method. Only two following overloads of Assert.Catch() method are partially supported and are translated in syntactically correct (but not semantically equivallent) C++ code:

  • Exception Catch(TestDelegate code);
  • T Catch<T>(TestDelegate code) where T : Exception;

All other overloads are not supported and are translated into syntactically incorrect C++ code.

Invocation of either of two supported overloads in C# code is translated into invocation of the same C++ static method bool AssertThrows(std::function<void()> func). So the following C# code

Assert.Catch(SomeClass.SomeMethod);
Assert.Catch<NullReferenceException>(SomeClass.SomeMethod);

will be translated into the following two identical lines of C++ code

System::TestTools::AssertThrows(SomeClass::SomeMethod);
System::TestTools::AssertThrows(SomeClass::SomeMethod);

Note that C++ method System::TestTools::AssertThrows() is semantically different from Assert.Catch() in that it catches all exceptions and it returns a bool value indicating if any exeception was caught, while NUnit's Assert.Catch() returns a caught Exception object.

NUnit's Assert.Greater() method support is limited

Following Assert.Greater() method's overloads that accept reference to IComparable interface are not supported by Porter

  • void Greater(IComparable arg1, IComparable arg2);
  • void Greater(IComparable arg1, IComparable arg2, string message);
  • void Greater(IComparable arg1, IComparable arg2, string message, params object[] args);

Invocation of these overloads are translated into syntactically incorrect C++ code.

NUnit's Assert.GreaterOrEqual() method support is limited

Following Assert.GreaterOrEqual() method's overloads that accept reference to IComparable interface are not supported by Porter

  • void GreaterOrEqual(IComparable arg1, IComparable arg2);
  • void GreaterOrEqual(IComparable arg1, IComparable arg2, string message);
  • void GreaterOrEqual(IComparable arg1, IComparable arg2, string message, params object[] args);

Invocation of these overloads are translated into syntactically incorrect C++ code.

NUnit's Assert.Ignore() method is not supported

Method Assert.Ignore() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.Ignore() method in C# code.

NUnit's Assert.Inconclusive() method is not supported

Method Assert.Inconclusive() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.Inconclusive() method in C# code.

NUnit's Assert.IsAssignableFrom() method is not supported

Method Assert.IsAssignableFrom() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.IsAssignableFrom() method in C# code.

NUnit's Assert.IsInstanceOf() method is not supported

Method Assert.IsInstanceOf() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.IsInstanceOf() method in C# code.

NUnit's Assert.IsInstanceOfType() method is not supported

Method Assert.IsInstanceOfType() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.IsInstanceOfType() method in C# code.

NUnit's Assert.IsNaN() method is not supported

Method Assert.IsNaN() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.IsNaN() method in C# code.

NUnit's Assert.IsNotAssignableFrom() method is not supported

Method Assert.IsNotAssignableFrom() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.IsNotAssignableFrom() method in C# code.

NUnit's Assert.IsNotInstanceOf() method is not supported

Method Assert.IsNotInstanceOf() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.IsNotInstanceOf() method in C# code.

NUnit's Assert.IsNotInstanceOfType() method is not supported

Method Assert.IsNotInstanceOfType() is not supported by Porter. Porter will generate an error message when encounters invocation of Assert.IsNotInstanceOfType() method in C# code.

NUnit's Assert.Less() method support is limited

Following Assert.Less() method's overloads that accept reference to IComparable interface are not supported by Porter

  • void Less(IComparable arg1, IComparable arg2);
  • void Less(IComparable arg1, IComparable arg2, string message);
  • void Less(IComparable arg1, IComparable arg2, string message, params object[] args);

Invocation of these overloads are translated into syntactically incorrect C++ code.

NUnit's Assert.LessOrEqual() method support is limited

Following Assert.LessOrEqual() method's overloads that accept reference to IComparable interface are not supported by Porter

  • void LessOrEqual(IComparable arg1, IComparable arg2);
  • void LessOrEqual(IComparable arg1, IComparable arg2, string message);
  • void LessOrEqual(IComparable arg1, IComparable arg2, string message, params object[] args);

Invocation of these overloads are translated into syntactically incorrect C++ code.

NUnit's Assert.That() method support is limited

Porter does not support following four overloads of method Assert.That()

  • void That(TestDelegate code, IResolveConstraint constraint);
  • void That<T>(ActualValueDelegate<T> del, IResolveConstraint expr);
  • void That<T>(ActualValueDelegate<T> del, IResolveConstraint expr, string message);
  • void That<T>(ActualValueDelegate<T> del, IResolveConstraint expr, string message, params object[] args);

All four are translated to syntactically incorrect C++ code.

Support of NUnit's constraint-based assertion model is limited

Porter supports only four types of constraints used in NUnit's Constraint-based assertion model. Following three of them are completely supported:

  • TrueConstraint
  • FalseConstraint
  • NullConstraint

The fourth constraint type - EqualsConstraint - has quite limited support. It is only translated when invocation expression Is.Equals() is used as the second argument of Assert.That() method.

So the following C# code will be correctly translated

Assert.That(5, Is.EqualTo(5));

while translation of this C# code will fail

NUnit.Framework.Constraints.EqualConstraint ec = Is.EqualTo(5);
Assert.That(5, ec);

as well as translation of this code

Assert.That(5, new NUnit.Framework.Constraints.EqualConstraint(5));

Also, no modifiers for EqualsConstraint are supproted.

NUnit's Assert.Throws() method support is limited

Out of nine overloads of Assert.Throws() method (including three generic ones) defined by NUnit framework Porter supports following six

  • Exception Throws(Type expectedExceptionType, TestDelegate code);
  • Exception Throws(Type expectedExceptionType, TestDelegate code, string message);
  • Exception Throws(Type expectedExceptionType, TestDelegate code, string message, params object[] args);
  • T Throws<T>(TestDelegate code) where T : Exception;
  • T Throws<T>(TestDelegate code, string message) where T : Exception;
  • T Throws<T>(TestDelegate code, string message, params object[] args) where T : Exception;

Other three overloads that take reference to IResolveConstraint interface as their first argument are not supported.

Also Porter does not support TestDelegate delegate type declared by NUnit framework. C# code that explicitly mentions this type by name will be translated into syntactically incorrect C++ code. Thus, following C# code is not supported by Porter due to explicit mentioning of TestDelegate type

using System;
using NUnit.Framework;

namespace ns
{
   class Tested
    {
       public static void M()
        {
           throw new NullReferenceException();
        }
    }

    [TestFixture]

   class Tests
    {
        [Test]
       public void Test()
        {
           // Following line is not supported by Porter and will be translated into
           // incorrect C++ code.
           Assert.Throws<NullReferenceException>(new TestDelegate(Tested.M));
        }
    }
}

To overcome this limitation one can use anonymous delegates

[Test]
public void Test()
{
   // Following line will be translated correctly.
   Assert.Throws<NullReferenceException>(delegate {Tested.M();});
}

or rely on implicit casting

[Test]
public void Test()
{
   // Following line will be translated correctly.
   Assert.Throws<NullReferenceException>(Tested.M);
}

NUnit's ExpectedException attribute support is limited

There are some limitations with regard to support of ExpectedException method attribute. 

Out of six parameters of ExpectedException attribute, only following three are supported:

  • ExpectedException
  • ExpectedExceptionName
  • ExpectedMessage

However, only one of the three supported parameters can be specified for each method marked with ExpectedException attribute. Either named or unnamed, only the first parameter from the list of parameters passed to ExpectedException attribute constructor will be processed by Porter. The rest of the parameters if present will be ignored.

Thus each of the following definitions of method M() in C#
   

[ExpectedException(ExpectedException = typeof(NullReferenceException), ExpectedMessage = "Message")]
public void M()
{
}
[ExpectedException("System.NullReferenceException))]
public void M()
{
}

will be translated into identical C++ code

TEST_F(Tests, M)
{
    ASSERT_THROW({
        M();
    }, System::NullReferenceException);
}

ExpectedMessage parameter is ignored by Porter because it goes second in the list of the attribute parameters.

The following definition in C#

[ExpectedException(ExpectedMessage = "Message", ExpectedException = typeof(NullReferenceException))]
public void M()
{
}

will be translated into following C++ code

TEST_F(Tests, M)
{
   try
    {
        M();
    }
   catch (System::Exception& ex)
    {
        ASSERT_EQ(u"Message", ex.get_Message());
    }
}

In this example time ExpectedException parameter is ignored by Porter because it goes second in the list of attribute's parameters.

NUnit's Explicit class attribute is not supported

Attribute Explicit when applied to a C# class is ignored by Porter. Porter processes a C# class marked with Explicit attribute as if the class was not marked with this attribute.

NUnit's Ignore class attribute is not supported

Attribute Ignore when applied to a C# class is ignored by Porter. Porter processes a C# class marked with Ignore attribute as if the class was not marked with this attribute.

C# method marked with NUnit's TestFixtureSetUp attribute is always translated into C++ static method

Text fixture method marked with TestFixtureSetUp attribute in C# language is always translated into a static method in C++ regardless of whether it was static or instance method of a C# class. It means that if an instance method in a C# class marked with TestFixtureSetUp attribute tries to access any non-static members of the class, such method will be translated into syntactically incorrect C++ method.

C# method marked with NUnit's TestFixtureTearDown attribute is always translated into C++ static method

Text fixture method marked with TestFixtureTearDown attribute in C# language is always translated into a static method in C++ regardless of whether it was static or instance method of a C# class. It means that if an instance method in a C# class marked with TestFixtureTearDown attribute tries to access any non-static members of the class, such method will be translated into syntactically incorrect C++ method.

Support of xUnit library is limited

xUnit library is not completely supported by Porter

Compound assignment operators LHS is translated twice

LHS expression of compound assignment operators is translated twice. If it is a modifier expression, side effects are applied twice. Use 'setter_wrap_with_lambda' option to bypass this limitation.

 

Tags:
Created by Tilal Ahmad on 2018/10/03 07:09