Skip to content

Scala to C# Conversion Guide

Alex Valuyskiy edited this page Jun 16, 2017 · 14 revisions
  • Be .NET idiomatic, e.g. do not port Duration instead of TimeSpan and Future instead of Task<T>
  • Stay as close as possible to the original JVM implementation,
  • Do not add features that don't exist in JVM Akka into the core Akka.NET

Case classes

from

final case class HandingOverData(singleton: ActorRef, name: String)

simple implementation

public sealed class HandingOverData
{
    public HandingOverData(IActorRef singleton, string name)
    {
        Singleton = singleton;
        Name = name;
    }

    public IActorRef Singleton { get; }

    public string Name { get; }
}

complex implementation

public sealed class HandingOverData
{
    public HandingOverData(IActorRef singleton, string name)
    {
        Singleton = singleton;
        Name = name;
    }

    public IActorRef Singleton { get; }

    public string Name { get; }

    private bool Equals(HandingOverData other)
    {
        return Equals(Singleton, other.Singleton) && string.Equals(Name, other.Name);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        return obj is HandingOverData && Equals((HandingOverData)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((Singleton?.GetHashCode() ?? 0) * 397) ^ (Name?.GetHashCode() ?? 0);
        }
    }

    public override string ToString() => $"{nameof(HandingOverData)}<{nameof(Singleton)}: {Singleton}, {nameof(Name)}: {Name}>";
}

In order to support C# 7 deconstruction you could add Deconstruct method

public void Deconstruct(out IActorRef singleton, out string name)
{
    singleton = Singleton;
    name = Name;
}

Some messages should implement With (from C# 8 records spec) or Copy method.

public HandingOverData With(IActorRef singleton = null, string name = null) 
    => new HandingOverData(singleton ?? Singleton, name ?? Name);

In some cases it would be a good idea (mandatory for value types) to implement IEquatable<T>

public sealed class HandingOverData : IEquatable<HandingOverData>
{
    ...
    public bool Equals(HandingOverData other)
    {
        return Equals(Singleton, other.Singleton) && string.Equals(Name, other.Name);
    }
    ...
}

case object

from

case object RecoveryCompleted

simple implementation

public sealed class RecoveryCompleted
{
    public static RecoveryCompleted Instance { get; } = new RecoveryCompleted();
    private RecoveryCompleted() {}
}

complex implementation

public sealed class RecoveryCompleted
{
    public static RecoveryCompleted Instance { get; } = new RecoveryCompleted();

    private RecoveryCompleted() {}
    public override bool Equals(object obj) => !ReferenceEquals(obj, null) && obj is RecoveryCompleted;
    public override int GetHashCode() => nameof(RecoveryCompleted).GetHashCode();
    public override string ToString() => nameof(RecoveryCompleted);
}

In some cases it would be a good idea (mandatory for value types) to implement IEquatable<T>

public sealed class RecoveryCompleted : IEquatable<RecoveryCompleted>
{
    ...
    public bool Equals(RecoveryCompleted other) => true;
    ...
}

LINQ (collection's methods)

scala C#
flatten
sorted
flatMap SelectMany
map(func) Select(func)
filter Where
forall All
Any
head First
headOption FirstOrDefault
last Last
lastOption LastOrDefault
reduce
reduceLeft()(func) Aggregate(func)
reduceRight Reverse().Aggregate(func)
fold
foldLeft(initval)(func) Aggregate(initval, func)
foldRight Reverse().Aggregate(initval, func)
foreach <none>
drop(count) Skip(count)
dropRight(count) <none>
take(count) Take(count)
takeRight(count) Reverse().Skip(count).Reverse()
zip(seq2, func) Zip(seq2, func)
zipWithIndex <none>

Currying

from

def bufferOr(grouping: String, message: Any, originalSender: ActorRef)(action:  Unit): Unit = {
    buffers.get(grouping) match {
      case None  action
      case Some(messages) 
        buffers = buffers.updated(grouping, messages :+ ((message, originalSender)))
        totalBufferSize += 1
    }
}

to

public void BufferOr(string grouping, object message, IActorRef originalSender, Action action)
{
    BufferedMessages messages = null;
    if (_buffers.TryGetValue(grouping, out messages))
    {
        _buffers[grouping].Add(new KeyValuePair<object, IActorRef>(message, originalSender));
        _totalBufferSize += 1;
    }
    else {
        action();
    }  
}

Exceptions

scala C#
IllegalArgumentException ArgumentException
IllegalStateException InvalidOperationException
ArithmeticException ArithmeticException
NullPointerException NullReferenceException
NotSerializableException SerializationException

Pattern matching

constant patterns

def testPattern(x: Any): String = x match {
   case 0 => "zero"
   case true => "true"
   case "hello" => "you said 'hello'"
   case Nil => "an empty List"
}

C# 7 supports all constant patterns

public string TestPattern(object x)
{
    switch(x)
    {
        case 0: return "zero";
        case true: return "true";
        case "hello": return "you said Hello";
        case null: return "an empty list";                
    }
    return string.Empty;
}

sequence patterns

def testPattern(x: Any): String = x match {
   case List(0, _, _) => "a three-element list with 0 as the first element"
   case List(1, _*) => "a list beginning with 1, having any number of elements"
   case Vector(1, _*) => "a vector starting with 1, having any number of elements"
}

C# does not support sequence patterns

tuples patterns

def testPattern(x: Any): String = x match {
   case (a, b) => s"got $a and $b"
   case (a, b, c) => s"got $a, $b, and $c"
}

C# does not support tuples patterns

constructor patterns (case classes)

def testPattern(x: Any): String = x match {
   case Person(first, "Alexander") => s"found an Alexander, first name = $first"
   case Dog("Suka") => "found a dog named Suka"
}

C# does not support constructor patterns. But you could use an equivalent

public string TestPattern(object x)
{
    switch(x)
    {
        case Person p when p.LastName == "Alexander": return $"found an Alexander, first name = {p.FirstName}";
        case Dog d when d.Name == "Suka": return "found a dog named Suka";  
    }
    return string.Empty;
}

typed patterns

def testPattern(x: Any): String = x match {
   case s: String => s"you gave me this string: $s"
   case i: Int => s"thanks for the int: $i"
   case f: Float => s"thanks for the float: $f"
   case a: Array[Int] => s"an array of int: ${a.mkString(",")}"
   case as: Array[String] => s"an array of strings: ${as.mkString(",")}"
   case d: Dog => s"dog: ${d.name}"
   case list: List[_] => s"thanks for the List: $list"
   case m: Map[_, _] => m.toString
}

You can use typed patterns in C#7

public string TestPattern(object x)
{
    switch(x)
    { 
        case string s: return $"you gave me this string: {s}";
        case int i: return $"thanks for the int: {i}";
        case float f: return $"thanks for the float: {f}";
        case int[] a: return $"an array of int: {string.Join(",", a)}";
        case Dog d: return $"dog: ${d.Name}";
        case List<int> list: return $"thanks for the List: {list}";
        case Dictionary<int, string> dict: return $"dictionary: {dict}";
    }
    return string.Empty;
}

extractors

trait User {
  def name: String
}
class FreeUser(val name: String) extends User
class PremiumUser(val name: String) extends User

object FreeUser {
  def unapply(user: FreeUser): Option[String] = Some(user.name)
}
object PremiumUser {
  def unapply(user: PremiumUser): Option[String] = Some(user.name)
}

// using
val user: User = new PremiumUser("Daniel")
user match {
  case FreeUser(name) => "Hello " + name
  case PremiumUser(name) => "Welcome back, dear " + name
}
public interface IUser
{
    string Name { get; }
}

public class FreeUser : IUser
{
    public FreeUser(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

public class PremiumUser : IUser
{
    public PremiumUser(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

public static class UserExtensions
{
    public static bool TryExtractName(this IUser user, out string name)
    {
        name = user.Name;
        return !string.IsNullOrEmpty(user.Name);
    }
}


// using
IUser user = new PremiumUser("Alex");

switch (user)
{
    case PremiumUser p when p.TryExtractName(out var name):
        Console.WriteLine(name);
        break;
}

Traits

There is no equivalent in C# to replace a trait. Good luck.

Partial functions

There is no equivalent in C# to replace a partial function. Good luck.

Class constructors

case class AllForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Duration)

C# equivalent. All constructor parameters should be transformed to public properties

public class AllForOneStrategy
{
    public AllForOneStrategy(int maxNumberOfRetries, TimeSpan withinTimeRange)
    {
        MaxNumberOfRetries = maxNumberOfRetries;
        WithinTimeRange = withinTimeRange;   
    }
    
    public int MaxNumberOfRetries { get; }
    public TimeSpan WithinTimeRange { get; }
}

Require

require(cost > 0, "cost must be > 0")

use ArgumentException

if (cost <= 0) throw ArgumentException("cost must be > 0", nameof(cost));

Tests

  • Prefer to use FluentAssertions in tests, instead of Xunit assertions and AkkaSpecExtensions

intercept[T]

Akka uses intecept to check that an exception was thrown

intercept[IllegalArgumentException] {
  val serializer = new MiscMessageSerializer(system.asInstanceOf[ExtendedActorSystem])
  serializer.manifest("INVALID")
}

in C# you have 2 options

var serializer = new MiscMessageSerializer(Sys.AsInstanceOf<ExtendedActorSystem>());

// use FluentAssertions
Action comparison = () => serializer.Manifest("INVALID");
comparison.ShouldThrow<ArgumentException>();

// use Xunit2 asserts
Assert.Throws<ArgumentException>(() => serializer.Manifest("INVALID"));
Clone this wiki locally