diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln
new file mode 100644
index 0000000000000000000000000000000000000000..5890617acf5ef0f88090e7e130bd364ff63e818c
--- /dev/null
+++ b/src/csharp/Grpc.sln
@@ -0,0 +1,38 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcDemo", "GrpcDemo\GrpcDemo.csproj", "{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcApi", "GrpcApi\GrpcApi.csproj", "{7DC1433E-3225-42C7-B7EA-546D56E27A4B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcCore", "GrpcCore\GrpcCore.csproj", "{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcCoreTests", "GrpcCoreTests\GrpcCoreTests.csproj", "{86EC5CB4-4EA2-40A2-8057-86542A0353BB}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|x86.ActiveCfg = Debug|x86
+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|x86.Build.0 = Debug|x86
+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|x86.ActiveCfg = Release|x86
+		{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|x86.Build.0 = Release|x86
+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|x86.Build.0 = Debug|Any CPU
+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|x86.ActiveCfg = Release|Any CPU
+		{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|x86.Build.0 = Release|Any CPU
+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|x86.Build.0 = Debug|Any CPU
+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|x86.ActiveCfg = Release|Any CPU
+		{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|x86.Build.0 = Release|Any CPU
+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|x86.Build.0 = Debug|Any CPU
+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|x86.ActiveCfg = Release|Any CPU
+		{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|x86.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(MonoDevelopProperties) = preSolution
+		StartupItem = GrpcDemo\GrpcDemo.csproj
+	EndGlobalSection
+EndGlobal
diff --git a/src/csharp/GrpcApi/.gitignore b/src/csharp/GrpcApi/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2cc8cca52d01ba50f6398e7cb7a147161dad578c
--- /dev/null
+++ b/src/csharp/GrpcApi/.gitignore
@@ -0,0 +1,2 @@
+test-results
+bin
diff --git a/src/csharp/GrpcApi/DummyMathServiceClient.cs b/src/csharp/GrpcApi/DummyMathServiceClient.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6799109be42af262867790922875e56a4794f96b
--- /dev/null
+++ b/src/csharp/GrpcApi/DummyMathServiceClient.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+
+namespace math
+{
+//	/// <summary>
+//	/// Dummy local implementation of math service.
+//	/// </summary>
+//	public class DummyMathServiceClient : IMathServiceClient
+//	{
+//		public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken))
+//		{
+//			// TODO: cancellation...
+//			return DivInternal(args);
+//		}
+//		
+//		public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken))
+//		{
+//			return Task.Factory.StartNew(() => DivInternal(args), token);
+//		}
+//
+//		public IObservable<Num> Fib(FibArgs args, CancellationToken token = default(CancellationToken))
+//		{
+//			if (args.Limit > 0)
+//			{
+//				// TODO: cancellation
+//				return FibInternal(args.Limit).ToObservable();
+//			}
+//
+//			throw new NotImplementedException("Not implemented yet");
+//		}
+//
+//		public Task<Num> Sum(IObservable<Num> inputs, CancellationToken token = default(CancellationToken))
+//		{
+//			// TODO: implement
+//			inputs = null;
+//			return Task.Factory.StartNew(() => Num.CreateBuilder().Build(), token);
+//		}
+//
+//		public IObservable<DivReply> DivMany(IObservable<DivArgs> inputs, CancellationToken token = default(CancellationToken))
+//		{
+//			// TODO: implement
+//			inputs = null;
+//			return new List<DivReply> { }.ToObservable ();
+//		}
+//
+//
+//		DivReply DivInternal(DivArgs args)
+//		{
+//			long quotient = args.Dividend / args.Divisor;
+//			long remainder = args.Dividend % args.Divisor;
+//			return new DivReply.Builder{ Quotient = quotient, Remainder = remainder }.Build();
+//		}
+//
+//		IEnumerable<Num> FibInternal(long n)
+//		{
+//			long a = 0;
+//			yield return new Num.Builder{Num_=a}.Build();
+//
+//			long b = 1;
+//			for (long i = 0; i < n - 1; i++)
+//			{
+//				long temp = a;
+//				a = b;
+//				b = temp + b;
+//				yield return new Num.Builder{Num_=a}.Build();
+//			}
+//		}
+//	}
+}
+
diff --git a/src/csharp/GrpcApi/Examples.cs b/src/csharp/GrpcApi/Examples.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d45b702708e0afd66959cfc2afc295a509034380
--- /dev/null
+++ b/src/csharp/GrpcApi/Examples.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+
+namespace math
+{
+	public class Examples
+	{
+		public static void DivExample(IMathServiceClient stub)
+		{
+			DivReply result = stub.Div(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build());
+			Console.WriteLine("Div Result: " + result);
+		}
+
+		public static void DivAsyncExample(IMathServiceClient stub)
+		{
+			Task<DivReply> call = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
+			DivReply result = call.Result;
+			Console.WriteLine(result);
+		}
+
+		public static void DivAsyncWithCancellationExample(IMathServiceClient stub)
+		{
+			Task<DivReply> call = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
+			DivReply result = call.Result;
+			Console.WriteLine(result);
+		}
+
+		public static void FibExample(IMathServiceClient stub)
+		{
+            var recorder = new RecordingObserver<Num>();
+            stub.Fib(new FibArgs.Builder { Limit = 5 }.Build(), recorder);
+
+			List<Num> numbers = recorder.ToList().Result;
+            Console.WriteLine("Fib Result: " + string.Join("|", recorder.ToList().Result));
+		}
+
+		public static void SumExample(IMathServiceClient stub)
+		{
+			List<Num> numbers = new List<Num>{new Num.Builder { Num_ = 1 }.Build(), 
+				new Num.Builder { Num_ = 2 }.Build(),
+				new Num.Builder { Num_ = 3 }.Build()};
+			
+            var res = stub.Sum();
+            foreach (var num in numbers) {
+                res.Inputs.OnNext(num);
+            }
+            res.Inputs.OnCompleted();
+
+			Console.WriteLine("Sum Result: " + res.Task.Result);
+		}
+
+		public static void DivManyExample(IMathServiceClient stub)
+		{
+			List<DivArgs> divArgsList = new List<DivArgs>{
+				new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build(),
+				new DivArgs.Builder { Dividend = 100, Divisor = 21 }.Build(),
+				new DivArgs.Builder { Dividend = 7, Divisor = 2 }.Build()
+			};
+
+            var recorder = new RecordingObserver<DivReply>();
+			
+            var inputs = stub.DivMany(recorder);
+            foreach (var input in divArgsList)
+            {
+                inputs.OnNext(input);
+            }
+            inputs.OnCompleted();
+
+			Console.WriteLine("DivMany Result: " + string.Join("|", recorder.ToList().Result));
+		}
+
+		public static void DependendRequestsExample(IMathServiceClient stub)
+		{
+			var numberList = new List<Num>
+			{ new Num.Builder{ Num_ = 1 }.Build(), 
+				new Num.Builder{ Num_ = 2 }.Build(), new Num.Builder{ Num_ = 3 }.Build()
+			};
+
+			numberList.ToObservable();
+
+			//IObserver<Num> numbers;
+			//Task<Num> call = stub.Sum(out numbers);            
+			//foreach (var num in numberList)
+			//{
+			//	numbers.OnNext(num);
+			//}
+			//numbers.OnCompleted();
+
+			//Num sum = call.Result;
+
+			//DivReply result = stub.Div(new DivArgs.Builder { Dividend = sum.Num_, Divisor = numberList.Count }.Build());
+		}
+	}
+}
+
diff --git a/src/csharp/GrpcApi/GrpcApi.csproj b/src/csharp/GrpcApi/GrpcApi.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..d0377828b5463547a2e6c723ed6d94695452d0b7
--- /dev/null
+++ b/src/csharp/GrpcApi/GrpcApi.csproj
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{7DC1433E-3225-42C7-B7EA-546D56E27A4B}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>GrpcApi</RootNamespace>
+    <AssemblyName>GrpcApi</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Reactive.Linq, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Data.Linq" />
+    <Reference Include="System.Reactive.Interfaces, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Core, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
+      <Private>False</Private>
+    </Reference>
+    <Reference Include="Google.ProtocolBuffers">
+      <HintPath>..\lib\Google.ProtocolBuffers.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Examples.cs" />
+    <Compile Include="IMathServiceClient.cs" />
+    <Compile Include="Math.cs" />
+    <Compile Include="DummyMathServiceClient.cs" />
+    <Compile Include="MathServiceClientStub.cs" />
+    <Compile Include="RecordingObserver.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\GrpcCore\GrpcCore.csproj">
+      <Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project>
+      <Name>GrpcCore</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcApi/IMathServiceClient.cs b/src/csharp/GrpcApi/IMathServiceClient.cs
new file mode 100644
index 0000000000000000000000000000000000000000..51385a328f49a20991a88607c6418fc28b714071
--- /dev/null
+++ b/src/csharp/GrpcApi/IMathServiceClient.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using Google.GRPC.Core;
+
+namespace math
+{
+	/// <summary>
+	/// Hand-written stub for MathService defined in math.proto.
+	/// This code will be generated by gRPC codegen in the future.
+	/// </summary>
+	public interface IMathServiceClient
+	{
+		DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken));
+
+		Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken));
+
+		Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken));
+
+		ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken));
+
+		IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken));
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcApi/Math.cs b/src/csharp/GrpcApi/Math.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2d700337ac7953c343353240645f12803df70130
--- /dev/null
+++ b/src/csharp/GrpcApi/Math.cs
@@ -0,0 +1,1531 @@
+// Generated by ProtoGen, Version=2.4.1.521, Culture=neutral, PublicKeyToken=17b3b1f090c3ea48.  DO NOT EDIT!
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.ProtocolBuffers;
+using pbc = global::Google.ProtocolBuffers.Collections;
+using pbd = global::Google.ProtocolBuffers.Descriptors;
+using scg = global::System.Collections.Generic;
+namespace math {
+  
+  namespace Proto {
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public static partial class Math {
+    
+      #region Extension registration
+      public static void RegisterAllExtensions(pb::ExtensionRegistry registry) {
+      }
+      #endregion
+      #region Static variables
+      internal static pbd::MessageDescriptor internal__static_math_DivArgs__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.DivArgs, global::math.DivArgs.Builder> internal__static_math_DivArgs__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_DivReply__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.DivReply, global::math.DivReply.Builder> internal__static_math_DivReply__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_FibArgs__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.FibArgs, global::math.FibArgs.Builder> internal__static_math_FibArgs__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_Num__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.Num, global::math.Num.Builder> internal__static_math_Num__FieldAccessorTable;
+      internal static pbd::MessageDescriptor internal__static_math_FibReply__Descriptor;
+      internal static pb::FieldAccess.FieldAccessorTable<global::math.FibReply, global::math.FibReply.Builder> internal__static_math_FibReply__FieldAccessorTable;
+      #endregion
+      #region Descriptor
+      public static pbd::FileDescriptor Descriptor {
+        get { return descriptor; }
+      }
+      private static pbd::FileDescriptor descriptor;
+      
+      static Math() {
+        byte[] descriptorData = global::System.Convert.FromBase64String(
+            string.Concat(
+              "CgptYXRoLnByb3RvEgRtYXRoIiwKB0RpdkFyZ3MSEAoIZGl2aWRlbmQYASAB", 
+              "KAMSDwoHZGl2aXNvchgCIAEoAyIvCghEaXZSZXBseRIQCghxdW90aWVudBgB", 
+              "IAEoAxIRCglyZW1haW5kZXIYAiABKAMiGAoHRmliQXJncxINCgVsaW1pdBgB", 
+              "IAEoAyISCgNOdW0SCwoDbnVtGAEgASgDIhkKCEZpYlJlcGx5Eg0KBWNvdW50", 
+              "GAEgASgDMqQBCgRNYXRoEiYKA0RpdhINLm1hdGguRGl2QXJncxoOLm1hdGgu", 
+              "RGl2UmVwbHkiABIuCgdEaXZNYW55Eg0ubWF0aC5EaXZBcmdzGg4ubWF0aC5E", 
+              "aXZSZXBseSIAKAEwARIjCgNGaWISDS5tYXRoLkZpYkFyZ3MaCS5tYXRoLk51", 
+            "bSIAMAESHwoDU3VtEgkubWF0aC5OdW0aCS5tYXRoLk51bSIAKAE="));
+        pbd::FileDescriptor.InternalDescriptorAssigner assigner = delegate(pbd::FileDescriptor root) {
+          descriptor = root;
+          internal__static_math_DivArgs__Descriptor = Descriptor.MessageTypes[0];
+          internal__static_math_DivArgs__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.DivArgs, global::math.DivArgs.Builder>(internal__static_math_DivArgs__Descriptor,
+                  new string[] { "Dividend", "Divisor", });
+          internal__static_math_DivReply__Descriptor = Descriptor.MessageTypes[1];
+          internal__static_math_DivReply__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.DivReply, global::math.DivReply.Builder>(internal__static_math_DivReply__Descriptor,
+                  new string[] { "Quotient", "Remainder", });
+          internal__static_math_FibArgs__Descriptor = Descriptor.MessageTypes[2];
+          internal__static_math_FibArgs__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.FibArgs, global::math.FibArgs.Builder>(internal__static_math_FibArgs__Descriptor,
+                  new string[] { "Limit", });
+          internal__static_math_Num__Descriptor = Descriptor.MessageTypes[3];
+          internal__static_math_Num__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.Num, global::math.Num.Builder>(internal__static_math_Num__Descriptor,
+                  new string[] { "Num_", });
+          internal__static_math_FibReply__Descriptor = Descriptor.MessageTypes[4];
+          internal__static_math_FibReply__FieldAccessorTable = 
+              new pb::FieldAccess.FieldAccessorTable<global::math.FibReply, global::math.FibReply.Builder>(internal__static_math_FibReply__Descriptor,
+                  new string[] { "Count", });
+          pb::ExtensionRegistry registry = pb::ExtensionRegistry.CreateInstance();
+          RegisterAllExtensions(registry);
+          return registry;
+        };
+        pbd::FileDescriptor.InternalBuildGeneratedFileFrom(descriptorData,
+            new pbd::FileDescriptor[] {
+            }, assigner);
+      }
+      #endregion
+      
+    }
+  }
+  #region Messages
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class DivArgs : pb::GeneratedMessage<DivArgs, DivArgs.Builder> {
+    private DivArgs() { }
+    private static readonly DivArgs defaultInstance = new DivArgs().MakeReadOnly();
+    private static readonly string[] _divArgsFieldNames = new string[] { "dividend", "divisor" };
+    private static readonly uint[] _divArgsFieldTags = new uint[] { 8, 16 };
+    public static DivArgs DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override DivArgs DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override DivArgs ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_DivArgs__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<DivArgs, DivArgs.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_DivArgs__FieldAccessorTable; }
+    }
+    
+    public const int DividendFieldNumber = 1;
+    private bool hasDividend;
+    private long dividend_;
+    public bool HasDividend {
+      get { return hasDividend; }
+    }
+    public long Dividend {
+      get { return dividend_; }
+    }
+    
+    public const int DivisorFieldNumber = 2;
+    private bool hasDivisor;
+    private long divisor_;
+    public bool HasDivisor {
+      get { return hasDivisor; }
+    }
+    public long Divisor {
+      get { return divisor_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _divArgsFieldNames;
+      if (hasDividend) {
+        output.WriteInt64(1, field_names[0], Dividend);
+      }
+      if (hasDivisor) {
+        output.WriteInt64(2, field_names[1], Divisor);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasDividend) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Dividend);
+        }
+        if (hasDivisor) {
+          size += pb::CodedOutputStream.ComputeInt64Size(2, Divisor);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static DivArgs ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static DivArgs ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static DivArgs ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static DivArgs ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivArgs ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private DivArgs MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(DivArgs prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<DivArgs, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(DivArgs cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private DivArgs result;
+      
+      private DivArgs PrepareBuilder() {
+        if (resultIsReadOnly) {
+          DivArgs original = result;
+          result = new DivArgs();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override DivArgs MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.DivArgs.Descriptor; }
+      }
+      
+      public override DivArgs DefaultInstanceForType {
+        get { return global::math.DivArgs.DefaultInstance; }
+      }
+      
+      public override DivArgs BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is DivArgs) {
+          return MergeFrom((DivArgs) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(DivArgs other) {
+        if (other == global::math.DivArgs.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasDividend) {
+          Dividend = other.Dividend;
+        }
+        if (other.HasDivisor) {
+          Divisor = other.Divisor;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_divArgsFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _divArgsFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasDividend = input.ReadInt64(ref result.dividend_);
+              break;
+            }
+            case 16: {
+              result.hasDivisor = input.ReadInt64(ref result.divisor_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasDividend {
+        get { return result.hasDividend; }
+      }
+      public long Dividend {
+        get { return result.Dividend; }
+        set { SetDividend(value); }
+      }
+      public Builder SetDividend(long value) {
+        PrepareBuilder();
+        result.hasDividend = true;
+        result.dividend_ = value;
+        return this;
+      }
+      public Builder ClearDividend() {
+        PrepareBuilder();
+        result.hasDividend = false;
+        result.dividend_ = 0L;
+        return this;
+      }
+      
+      public bool HasDivisor {
+        get { return result.hasDivisor; }
+      }
+      public long Divisor {
+        get { return result.Divisor; }
+        set { SetDivisor(value); }
+      }
+      public Builder SetDivisor(long value) {
+        PrepareBuilder();
+        result.hasDivisor = true;
+        result.divisor_ = value;
+        return this;
+      }
+      public Builder ClearDivisor() {
+        PrepareBuilder();
+        result.hasDivisor = false;
+        result.divisor_ = 0L;
+        return this;
+      }
+    }
+    static DivArgs() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class DivReply : pb::GeneratedMessage<DivReply, DivReply.Builder> {
+    private DivReply() { }
+    private static readonly DivReply defaultInstance = new DivReply().MakeReadOnly();
+    private static readonly string[] _divReplyFieldNames = new string[] { "quotient", "remainder" };
+    private static readonly uint[] _divReplyFieldTags = new uint[] { 8, 16 };
+    public static DivReply DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override DivReply DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override DivReply ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_DivReply__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<DivReply, DivReply.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_DivReply__FieldAccessorTable; }
+    }
+    
+    public const int QuotientFieldNumber = 1;
+    private bool hasQuotient;
+    private long quotient_;
+    public bool HasQuotient {
+      get { return hasQuotient; }
+    }
+    public long Quotient {
+      get { return quotient_; }
+    }
+    
+    public const int RemainderFieldNumber = 2;
+    private bool hasRemainder;
+    private long remainder_;
+    public bool HasRemainder {
+      get { return hasRemainder; }
+    }
+    public long Remainder {
+      get { return remainder_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _divReplyFieldNames;
+      if (hasQuotient) {
+        output.WriteInt64(1, field_names[0], Quotient);
+      }
+      if (hasRemainder) {
+        output.WriteInt64(2, field_names[1], Remainder);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasQuotient) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Quotient);
+        }
+        if (hasRemainder) {
+          size += pb::CodedOutputStream.ComputeInt64Size(2, Remainder);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static DivReply ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivReply ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivReply ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static DivReply ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static DivReply ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivReply ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static DivReply ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static DivReply ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static DivReply ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static DivReply ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private DivReply MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(DivReply prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<DivReply, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(DivReply cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private DivReply result;
+      
+      private DivReply PrepareBuilder() {
+        if (resultIsReadOnly) {
+          DivReply original = result;
+          result = new DivReply();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override DivReply MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.DivReply.Descriptor; }
+      }
+      
+      public override DivReply DefaultInstanceForType {
+        get { return global::math.DivReply.DefaultInstance; }
+      }
+      
+      public override DivReply BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is DivReply) {
+          return MergeFrom((DivReply) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(DivReply other) {
+        if (other == global::math.DivReply.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasQuotient) {
+          Quotient = other.Quotient;
+        }
+        if (other.HasRemainder) {
+          Remainder = other.Remainder;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_divReplyFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _divReplyFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasQuotient = input.ReadInt64(ref result.quotient_);
+              break;
+            }
+            case 16: {
+              result.hasRemainder = input.ReadInt64(ref result.remainder_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasQuotient {
+        get { return result.hasQuotient; }
+      }
+      public long Quotient {
+        get { return result.Quotient; }
+        set { SetQuotient(value); }
+      }
+      public Builder SetQuotient(long value) {
+        PrepareBuilder();
+        result.hasQuotient = true;
+        result.quotient_ = value;
+        return this;
+      }
+      public Builder ClearQuotient() {
+        PrepareBuilder();
+        result.hasQuotient = false;
+        result.quotient_ = 0L;
+        return this;
+      }
+      
+      public bool HasRemainder {
+        get { return result.hasRemainder; }
+      }
+      public long Remainder {
+        get { return result.Remainder; }
+        set { SetRemainder(value); }
+      }
+      public Builder SetRemainder(long value) {
+        PrepareBuilder();
+        result.hasRemainder = true;
+        result.remainder_ = value;
+        return this;
+      }
+      public Builder ClearRemainder() {
+        PrepareBuilder();
+        result.hasRemainder = false;
+        result.remainder_ = 0L;
+        return this;
+      }
+    }
+    static DivReply() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class FibArgs : pb::GeneratedMessage<FibArgs, FibArgs.Builder> {
+    private FibArgs() { }
+    private static readonly FibArgs defaultInstance = new FibArgs().MakeReadOnly();
+    private static readonly string[] _fibArgsFieldNames = new string[] { "limit" };
+    private static readonly uint[] _fibArgsFieldTags = new uint[] { 8 };
+    public static FibArgs DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override FibArgs DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override FibArgs ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_FibArgs__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<FibArgs, FibArgs.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_FibArgs__FieldAccessorTable; }
+    }
+    
+    public const int LimitFieldNumber = 1;
+    private bool hasLimit;
+    private long limit_;
+    public bool HasLimit {
+      get { return hasLimit; }
+    }
+    public long Limit {
+      get { return limit_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _fibArgsFieldNames;
+      if (hasLimit) {
+        output.WriteInt64(1, field_names[0], Limit);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasLimit) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Limit);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static FibArgs ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static FibArgs ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static FibArgs ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static FibArgs ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibArgs ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private FibArgs MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(FibArgs prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<FibArgs, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(FibArgs cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private FibArgs result;
+      
+      private FibArgs PrepareBuilder() {
+        if (resultIsReadOnly) {
+          FibArgs original = result;
+          result = new FibArgs();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override FibArgs MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.FibArgs.Descriptor; }
+      }
+      
+      public override FibArgs DefaultInstanceForType {
+        get { return global::math.FibArgs.DefaultInstance; }
+      }
+      
+      public override FibArgs BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is FibArgs) {
+          return MergeFrom((FibArgs) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(FibArgs other) {
+        if (other == global::math.FibArgs.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasLimit) {
+          Limit = other.Limit;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_fibArgsFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _fibArgsFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasLimit = input.ReadInt64(ref result.limit_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasLimit {
+        get { return result.hasLimit; }
+      }
+      public long Limit {
+        get { return result.Limit; }
+        set { SetLimit(value); }
+      }
+      public Builder SetLimit(long value) {
+        PrepareBuilder();
+        result.hasLimit = true;
+        result.limit_ = value;
+        return this;
+      }
+      public Builder ClearLimit() {
+        PrepareBuilder();
+        result.hasLimit = false;
+        result.limit_ = 0L;
+        return this;
+      }
+    }
+    static FibArgs() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class Num : pb::GeneratedMessage<Num, Num.Builder> {
+    private Num() { }
+    private static readonly Num defaultInstance = new Num().MakeReadOnly();
+    private static readonly string[] _numFieldNames = new string[] { "num" };
+    private static readonly uint[] _numFieldTags = new uint[] { 8 };
+    public static Num DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override Num DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override Num ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_Num__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<Num, Num.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_Num__FieldAccessorTable; }
+    }
+    
+    public const int Num_FieldNumber = 1;
+    private bool hasNum_;
+    private long num_;
+    public bool HasNum_ {
+      get { return hasNum_; }
+    }
+    public long Num_ {
+      get { return num_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _numFieldNames;
+      if (hasNum_) {
+        output.WriteInt64(1, field_names[0], Num_);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasNum_) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Num_);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static Num ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static Num ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static Num ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static Num ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static Num ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static Num ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static Num ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static Num ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static Num ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static Num ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private Num MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(Num prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<Num, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(Num cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private Num result;
+      
+      private Num PrepareBuilder() {
+        if (resultIsReadOnly) {
+          Num original = result;
+          result = new Num();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override Num MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.Num.Descriptor; }
+      }
+      
+      public override Num DefaultInstanceForType {
+        get { return global::math.Num.DefaultInstance; }
+      }
+      
+      public override Num BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is Num) {
+          return MergeFrom((Num) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(Num other) {
+        if (other == global::math.Num.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasNum_) {
+          Num_ = other.Num_;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_numFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _numFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasNum_ = input.ReadInt64(ref result.num_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasNum_ {
+        get { return result.hasNum_; }
+      }
+      public long Num_ {
+        get { return result.Num_; }
+        set { SetNum_(value); }
+      }
+      public Builder SetNum_(long value) {
+        PrepareBuilder();
+        result.hasNum_ = true;
+        result.num_ = value;
+        return this;
+      }
+      public Builder ClearNum_() {
+        PrepareBuilder();
+        result.hasNum_ = false;
+        result.num_ = 0L;
+        return this;
+      }
+    }
+    static Num() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+  public sealed partial class FibReply : pb::GeneratedMessage<FibReply, FibReply.Builder> {
+    private FibReply() { }
+    private static readonly FibReply defaultInstance = new FibReply().MakeReadOnly();
+    private static readonly string[] _fibReplyFieldNames = new string[] { "count" };
+    private static readonly uint[] _fibReplyFieldTags = new uint[] { 8 };
+    public static FibReply DefaultInstance {
+      get { return defaultInstance; }
+    }
+    
+    public override FibReply DefaultInstanceForType {
+      get { return DefaultInstance; }
+    }
+    
+    protected override FibReply ThisMessage {
+      get { return this; }
+    }
+    
+    public static pbd::MessageDescriptor Descriptor {
+      get { return global::math.Proto.Math.internal__static_math_FibReply__Descriptor; }
+    }
+    
+    protected override pb::FieldAccess.FieldAccessorTable<FibReply, FibReply.Builder> InternalFieldAccessors {
+      get { return global::math.Proto.Math.internal__static_math_FibReply__FieldAccessorTable; }
+    }
+    
+    public const int CountFieldNumber = 1;
+    private bool hasCount;
+    private long count_;
+    public bool HasCount {
+      get { return hasCount; }
+    }
+    public long Count {
+      get { return count_; }
+    }
+    
+    public override bool IsInitialized {
+      get {
+        return true;
+      }
+    }
+    
+    public override void WriteTo(pb::ICodedOutputStream output) {
+      int size = SerializedSize;
+      string[] field_names = _fibReplyFieldNames;
+      if (hasCount) {
+        output.WriteInt64(1, field_names[0], Count);
+      }
+      UnknownFields.WriteTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public override int SerializedSize {
+      get {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+        
+        size = 0;
+        if (hasCount) {
+          size += pb::CodedOutputStream.ComputeInt64Size(1, Count);
+        }
+        size += UnknownFields.SerializedSize;
+        memoizedSerializedSize = size;
+        return size;
+      }
+    }
+    
+    public static FibReply ParseFrom(pb::ByteString data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibReply ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibReply ParseFrom(byte[] data) {
+      return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();
+    }
+    public static FibReply ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();
+    }
+    public static FibReply ParseFrom(global::System.IO.Stream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibReply ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    public static FibReply ParseDelimitedFrom(global::System.IO.Stream input) {
+      return CreateBuilder().MergeDelimitedFrom(input).BuildParsed();
+    }
+    public static FibReply ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {
+      return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed();
+    }
+    public static FibReply ParseFrom(pb::ICodedInputStream input) {
+      return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();
+    }
+    public static FibReply ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+      return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();
+    }
+    private FibReply MakeReadOnly() {
+      return this;
+    }
+    
+    public static Builder CreateBuilder() { return new Builder(); }
+    public override Builder ToBuilder() { return CreateBuilder(this); }
+    public override Builder CreateBuilderForType() { return new Builder(); }
+    public static Builder CreateBuilder(FibReply prototype) {
+      return new Builder(prototype);
+    }
+    
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    public sealed partial class Builder : pb::GeneratedBuilder<FibReply, Builder> {
+      protected override Builder ThisBuilder {
+        get { return this; }
+      }
+      public Builder() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+      }
+      internal Builder(FibReply cloneFrom) {
+        result = cloneFrom;
+        resultIsReadOnly = true;
+      }
+      
+      private bool resultIsReadOnly;
+      private FibReply result;
+      
+      private FibReply PrepareBuilder() {
+        if (resultIsReadOnly) {
+          FibReply original = result;
+          result = new FibReply();
+          resultIsReadOnly = false;
+          MergeFrom(original);
+        }
+        return result;
+      }
+      
+      public override bool IsInitialized {
+        get { return result.IsInitialized; }
+      }
+      
+      protected override FibReply MessageBeingBuilt {
+        get { return PrepareBuilder(); }
+      }
+      
+      public override Builder Clear() {
+        result = DefaultInstance;
+        resultIsReadOnly = true;
+        return this;
+      }
+      
+      public override Builder Clone() {
+        if (resultIsReadOnly) {
+          return new Builder(result);
+        } else {
+          return new Builder().MergeFrom(result);
+        }
+      }
+      
+      public override pbd::MessageDescriptor DescriptorForType {
+        get { return global::math.FibReply.Descriptor; }
+      }
+      
+      public override FibReply DefaultInstanceForType {
+        get { return global::math.FibReply.DefaultInstance; }
+      }
+      
+      public override FibReply BuildPartial() {
+        if (resultIsReadOnly) {
+          return result;
+        }
+        resultIsReadOnly = true;
+        return result.MakeReadOnly();
+      }
+      
+      public override Builder MergeFrom(pb::IMessage other) {
+        if (other is FibReply) {
+          return MergeFrom((FibReply) other);
+        } else {
+          base.MergeFrom(other);
+          return this;
+        }
+      }
+      
+      public override Builder MergeFrom(FibReply other) {
+        if (other == global::math.FibReply.DefaultInstance) return this;
+        PrepareBuilder();
+        if (other.HasCount) {
+          Count = other.Count;
+        }
+        this.MergeUnknownFields(other.UnknownFields);
+        return this;
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input) {
+        return MergeFrom(input, pb::ExtensionRegistry.Empty);
+      }
+      
+      public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) {
+        PrepareBuilder();
+        pb::UnknownFieldSet.Builder unknownFields = null;
+        uint tag;
+        string field_name;
+        while (input.ReadTag(out tag, out field_name)) {
+          if(tag == 0 && field_name != null) {
+            int field_ordinal = global::System.Array.BinarySearch(_fibReplyFieldNames, field_name, global::System.StringComparer.Ordinal);
+            if(field_ordinal >= 0)
+              tag = _fibReplyFieldTags[field_ordinal];
+            else {
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              continue;
+            }
+          }
+          switch (tag) {
+            case 0: {
+              throw pb::InvalidProtocolBufferException.InvalidTag();
+            }
+            default: {
+              if (pb::WireFormat.IsEndGroupTag(tag)) {
+                if (unknownFields != null) {
+                  this.UnknownFields = unknownFields.Build();
+                }
+                return this;
+              }
+              if (unknownFields == null) {
+                unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);
+              }
+              ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name);
+              break;
+            }
+            case 8: {
+              result.hasCount = input.ReadInt64(ref result.count_);
+              break;
+            }
+          }
+        }
+        
+        if (unknownFields != null) {
+          this.UnknownFields = unknownFields.Build();
+        }
+        return this;
+      }
+      
+      
+      public bool HasCount {
+        get { return result.hasCount; }
+      }
+      public long Count {
+        get { return result.Count; }
+        set { SetCount(value); }
+      }
+      public Builder SetCount(long value) {
+        PrepareBuilder();
+        result.hasCount = true;
+        result.count_ = value;
+        return this;
+      }
+      public Builder ClearCount() {
+        PrepareBuilder();
+        result.hasCount = false;
+        result.count_ = 0L;
+        return this;
+      }
+    }
+    static FibReply() {
+      object.ReferenceEquals(global::math.Proto.Math.Descriptor, null);
+    }
+  }
+  
+  #endregion
+  
+  #region Services
+  /*
+  * Service generation is now disabled by default, use the following option to enable:
+  * option (google.protobuf.csharp_file_options).service_generator_type = GENERIC;
+  */
+  #endregion
+  
+}
+
+#endregion Designer generated code
diff --git a/src/csharp/GrpcApi/MathServiceClientStub.cs b/src/csharp/GrpcApi/MathServiceClientStub.cs
new file mode 100644
index 0000000000000000000000000000000000000000..493c186b8e5fd748f9056986000f9a890e497c9b
--- /dev/null
+++ b/src/csharp/GrpcApi/MathServiceClientStub.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using Google.GRPC.Core;
+
+namespace math
+{
+	/// <summary>
+	/// Implementation of math service stub (this is handwritten version of code 
+	/// that will normally be generated).
+	/// </summary>
+	public class MathServiceClientStub : IMathServiceClient
+	{
+		readonly Channel channel;
+        readonly TimeSpan methodTimeout;
+
+		public MathServiceClientStub(Channel channel, TimeSpan methodTimeout)
+		{
+			this.channel = channel;
+            this.methodTimeout = methodTimeout;
+		}
+
+		public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel);
+            return Calls.BlockingUnaryCall(call, args, token);
+		}
+
+		public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel);
+            return Calls.AsyncUnaryCall(call, args, token);
+		}
+
+        public Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<FibArgs, Num>("/math.Math/Fib", Serialize_FibArgs, Deserialize_Num, methodTimeout, channel);
+            return Calls.AsyncServerStreamingCall(call, args, outputs, token);
+		}
+
+        public ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)) 
+		{
+            var call = new Google.GRPC.Core.Call<Num, Num>("/math.Math/Sum", Serialize_Num, Deserialize_Num, methodTimeout, channel);
+            return Calls.AsyncClientStreamingCall(call, token);
+		}
+
+        public IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken))
+		{
+            var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/DivMany", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel);
+            return Calls.DuplexStreamingCall(call, outputs, token);
+		}
+
+        private static byte[] Serialize_DivArgs(DivArgs arg) {
+            return arg.ToByteArray();
+        }
+
+        private static byte[] Serialize_FibArgs(FibArgs arg) {
+            return arg.ToByteArray();
+        }
+
+        private static byte[] Serialize_Num(Num arg) {
+            return arg.ToByteArray();
+        }
+
+        private static DivReply Deserialize_DivReply(byte[] payload) {
+            return DivReply.CreateBuilder().MergeFrom(payload).Build();
+        }
+
+        private static Num Deserialize_Num(byte[] payload) {
+            return Num.CreateBuilder().MergeFrom(payload).Build();
+        }
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcApi/Messages.cs b/src/csharp/GrpcApi/Messages.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b08816bdb7ffda4a19a48986edf3dfcbe8d644bb
--- /dev/null
+++ b/src/csharp/GrpcApi/Messages.cs
@@ -0,0 +1,35 @@
+//using System;
+
+//namespace Google.GRPC.Examples.Math
+//{
+//	// Messages in this file are placeholders for actual protobuf message classes
+//	// that will be generated from math.proto file.
+//
+//	public class DivArgs
+//	{
+//		public long Dividend{ get; set; }
+//		public long Divisor { get; set; }
+//	}
+//	
+//	public class DivReply
+//	{
+//		public long Quotient { get; set; }
+//		public long Remainder { get; set; }
+//	}
+//	
+//	public class FibArgs
+//	{
+//		public long Limit { get; set; }
+//	}
+//	
+//	public class Number
+//	{
+//		public long Num { get; set; }
+//	}
+//	
+//	public class FibReply
+//	{
+//		public long Count { get; set; }
+//	}
+//}
+
diff --git a/src/csharp/GrpcApi/Properties/AssemblyInfo.cs b/src/csharp/GrpcApi/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..725f12c48606d2eabcdb06a7bb1ad6218570fdff
--- /dev/null
+++ b/src/csharp/GrpcApi/Properties/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle ("GrpcApi")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("jtattermusch")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion ("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/src/csharp/GrpcApi/RecordingObserver.cs b/src/csharp/GrpcApi/RecordingObserver.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8ba3787905a28bb06dd38f24854b9761456d5625
--- /dev/null
+++ b/src/csharp/GrpcApi/RecordingObserver.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace math
+{
+    public class RecordingObserver<T> : IObserver<T>
+    {
+        TaskCompletionSource<List<T>> tcs = new TaskCompletionSource<List<T>>();
+        List<T> data = new List<T>();
+
+        public void OnCompleted()
+        {
+            tcs.SetResult(data);
+        }
+
+        public void OnError(Exception error)
+        {
+            tcs.SetException(error);
+        }
+
+        public void OnNext(T value)
+        {
+            data.Add(value);
+        }
+
+        public Task<List<T>> ToList() {
+            return tcs.Task;
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcApi/math.proto b/src/csharp/GrpcApi/math.proto
new file mode 100755
index 0000000000000000000000000000000000000000..e98b99e002a14bfab203e276a507b5da9921b180
--- /dev/null
+++ b/src/csharp/GrpcApi/math.proto
@@ -0,0 +1,50 @@
+syntax = "proto2";
+
+package math;
+
+message DivArgs {
+  optional int64 dividend = 1;
+  optional int64 divisor = 2;
+}
+
+message DivReply {
+  optional int64 quotient = 1;
+  optional int64 remainder = 2;
+}
+
+message FibArgs {
+  optional int64 limit = 1;
+}
+
+message Num {
+  optional int64 num = 1;
+}
+
+message FibReply {
+  optional int64 count = 1;
+}
+
+service Math {
+  // Div divides args.dividend by args.divisor and returns the quotient and
+  // remainder.
+  rpc Div (DivArgs) returns (DivReply) {
+  }
+
+  // DivMany accepts an arbitrary number of division args from the client stream
+  // and sends back the results in the reply stream.  The stream continues until
+  // the client closes its end; the server does the same after sending all the
+  // replies.  The stream ends immediately if either end aborts.
+  rpc DivMany (stream DivArgs) returns (stream DivReply) {
+  }
+
+  // Fib generates numbers in the Fibonacci sequence.  If args.limit > 0, Fib
+  // generates up to limit numbers; otherwise it continues until the call is
+  // canceled.  Unlike Fib above, Fib has no final FibReply.
+  rpc Fib (FibArgs) returns (stream Num) {
+  }
+
+  // Sum sums a stream of numbers, returning the final result once the stream
+  // is closed.
+  rpc Sum (stream Num) returns (Num) {
+  }
+}
diff --git a/src/csharp/GrpcCore/.gitignore b/src/csharp/GrpcCore/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ba077a4031add5b3a04384f8b9cfc414efbf47dd
--- /dev/null
+++ b/src/csharp/GrpcCore/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/src/csharp/GrpcCore/Call.cs b/src/csharp/GrpcCore/Call.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bf257e5d5981a1ccfc2775256c6c33e9d0501f6f
--- /dev/null
+++ b/src/csharp/GrpcCore/Call.cs
@@ -0,0 +1,69 @@
+using System;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    public class Call<TRequest, TResponse>
+    {
+        readonly string methodName;
+        readonly Func<TRequest, byte[]> requestSerializer;
+        readonly Func<byte[], TResponse> responseDeserializer;
+        readonly TimeSpan timeout;
+        readonly Channel channel;
+
+        // TODO: channel param should be removed in the future.
+        public Call(string methodName, 
+                    Func<TRequest, byte[]> requestSerializer,
+                    Func<byte[], TResponse> responseDeserializer,
+                    TimeSpan timeout,
+                    Channel channel) {
+            this.methodName = methodName;
+            this.requestSerializer = requestSerializer;
+            this.responseDeserializer = responseDeserializer;
+            this.timeout = timeout;
+            this.channel = channel;
+        }
+
+
+        public Channel Channel
+        {
+            get
+            {
+                return this.channel;
+            }
+        }
+
+        public TimeSpan Timeout
+        {
+            get
+            {
+                return this.timeout;
+            }
+        }
+
+        public string MethodName
+        {
+            get
+            {
+                return this.methodName;
+            }
+        }
+
+        public Func<TRequest, byte[]> RequestSerializer
+        {
+            get
+            {
+                return this.requestSerializer;
+            }
+        }
+
+        public Func<byte[], TResponse> ResponseDeserializer
+        {
+            get
+            {
+                return this.responseDeserializer;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Calls.cs b/src/csharp/GrpcCore/Calls.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c3e51cb47811eaf3a9753bcca4ec8165cf705238
--- /dev/null
+++ b/src/csharp/GrpcCore/Calls.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    // NOTE: this class is work-in-progress
+
+    /// <summary>
+    /// Helper methods for generated stubs to make RPC calls.
+    /// </summary>
+    public static class Calls
+    {
+        public static TResponse BlockingUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token)
+        {
+            //TODO: implement this in real synchronous style once new GRPC C core API is available.
+            return AsyncUnaryCall(call, req, token).Result;
+        }
+
+        public static async Task<TResponse> AsyncUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            await asyncCall.WriteAsync(req);
+            await asyncCall.WritesCompletedAsync();
+
+            TResponse response = await asyncCall.ReadAsync();
+
+            Status status = await asyncCall.Finished;
+
+            if (status.StatusCode != StatusCode.GRPC_STATUS_OK)
+            {
+                throw new RpcException(status);
+            }
+            return response;
+        }
+
+        public static async Task AsyncServerStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, IObserver<TResponse> outputs, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            asyncCall.StartReadingToStream(outputs);
+
+            await asyncCall.WriteAsync(req);
+            await asyncCall.WritesCompletedAsync();
+        }
+
+        public static ClientStreamingAsyncResult<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            var task = asyncCall.ReadAsync();
+            var inputs = new StreamingInputObserver<TRequest, TResponse>(asyncCall);
+            return new ClientStreamingAsyncResult<TRequest, TResponse>(task, inputs);
+        }
+
+        public static TResponse BlockingClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, IObservable<TRequest> inputs, CancellationToken token)
+        {
+            throw new NotImplementedException();
+        }
+
+        public static IObserver<TRequest> DuplexStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, IObserver<TResponse> outputs, CancellationToken token)
+        {
+            var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer);
+            asyncCall.Initialize(call.Channel, call.MethodName);
+            asyncCall.Start(false, GetCompletionQueue());
+
+            asyncCall.StartReadingToStream(outputs);
+            var inputs = new StreamingInputObserver<TRequest, TResponse>(asyncCall);
+            return inputs;
+        }
+
+        private static CompletionQueueSafeHandle GetCompletionQueue() {
+            return GrpcEnvironment.ThreadPool.CompletionQueue;
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Channel.cs b/src/csharp/GrpcCore/Channel.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b0d8beeb7bf5e0bbc98cc4feae1a3e2c1fe266ae
--- /dev/null
+++ b/src/csharp/GrpcCore/Channel.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+	public class Channel : IDisposable
+	{
+        /// <summary>
+        /// Make sure GPRC environment is initialized before any channels get used.
+        /// </summary>
+        static Channel() {
+            GrpcEnvironment.EnsureInitialized();
+        }
+       
+        readonly ChannelSafeHandle handle;
+        readonly String target;
+
+        // TODO: add way how to create grpc_secure_channel....
+		// TODO: add support for channel args...
+		public Channel(string target)
+		{
+            this.handle = ChannelSafeHandle.Create(target, IntPtr.Zero);
+			this.target = target;
+		}
+
+        internal ChannelSafeHandle Handle
+        {
+            get
+            {
+                return this.handle;
+            }
+        }
+
+        public string Target
+        {
+            get
+            {
+                return this.target;
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (handle != null && !handle.IsInvalid)
+            {
+                handle.Dispose();
+            }
+        }
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs b/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9e7312c1fa079ea9828be82d950654a1f842d319
--- /dev/null
+++ b/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core
+{
+    /// <summary>
+    /// Return type for client streaming async method.
+    /// </summary>
+    public struct ClientStreamingAsyncResult<TRequest, TResponse>
+    {
+        readonly Task<TResponse> task;
+        readonly IObserver<TRequest> inputs;
+
+        public ClientStreamingAsyncResult(Task<TResponse> task, IObserver<TRequest> inputs)
+        {
+            this.task = task;
+            this.inputs = inputs;
+        }
+
+        public Task<TResponse> Task
+        {
+            get
+            {
+                return this.task;
+            }
+        }
+
+        public IObserver<TRequest> Inputs
+        {
+            get
+            {
+                return this.inputs;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/GrpcCore.csproj b/src/csharp/GrpcCore/GrpcCore.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..f0c84e78ea6c954251e7135fa260964e1eb569a4
--- /dev/null
+++ b/src/csharp/GrpcCore/GrpcCore.csproj
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>GrpcCore</RootNamespace>
+    <AssemblyName>GrpcCore</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="RpcException.cs" />
+    <Compile Include="Calls.cs" />
+    <Compile Include="Call.cs" />
+    <Compile Include="ClientStreamingAsyncResult.cs" />
+    <Compile Include="GrpcEnvironment.cs" />
+    <Compile Include="Status.cs" />
+    <Compile Include="StatusCode.cs" />
+    <Compile Include="Server.cs" />
+    <Compile Include="Channel.cs" />
+    <Compile Include="Internal\CallSafeHandle.cs" />
+    <Compile Include="Internal\ChannelSafeHandle.cs" />
+    <Compile Include="Internal\CompletionQueueSafeHandle.cs" />
+    <Compile Include="Internal\Enums.cs" />
+    <Compile Include="Internal\Event.cs" />
+    <Compile Include="Internal\SafeHandleZeroIsInvalid.cs" />
+    <Compile Include="Internal\Timespec.cs" />
+    <Compile Include="Internal\GrpcThreadPool.cs" />
+    <Compile Include="Internal\AsyncCall.cs" />
+    <Compile Include="Internal\ServerSafeHandle.cs" />
+    <Compile Include="Internal\StreamingInputObserver.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <Folder Include="Internal\" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/GrpcEnvironment.cs b/src/csharp/GrpcCore/GrpcEnvironment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7a644f49619f94356d6ddc6b85b884e1d58a62ad
--- /dev/null
+++ b/src/csharp/GrpcCore/GrpcEnvironment.cs
@@ -0,0 +1,91 @@
+using System;
+using Google.GRPC.Core.Internal;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core
+{
+    /// <summary>
+    /// Encapsulates initialization and shutdown of GRPC C core library.
+    /// You should not need to initialize it manually, as static constructors
+    /// should load the library when needed.
+    /// </summary>
+    public static class GrpcEnvironment
+    {
+        const int THREAD_POOL_SIZE = 1;
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_init();
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_shutdown();
+
+        static object staticLock = new object();
+        static bool initCalled = false;
+        static bool shutdownCalled = false;
+
+        static GrpcThreadPool threadPool = new GrpcThreadPool(THREAD_POOL_SIZE);
+
+        /// <summary>
+        /// Makes sure GRPC environment is initialized.
+        /// </summary>
+        public static void EnsureInitialized() {
+            lock(staticLock)
+            {
+                if (!initCalled)
+                {
+                    initCalled = true;
+                    GrpcInit();       
+                }
+            }
+        }
+
+        /// <summary>
+        /// Shuts down the GRPC environment if it was initialized before.
+        /// Repeated invocations have no effect.
+        /// </summary>
+        public static void Shutdown()
+        {
+            lock(staticLock)
+            {
+                if (initCalled && !shutdownCalled)
+                {
+                    shutdownCalled = true;
+                    GrpcShutdown();
+                }
+            }
+
+        }
+
+        /// <summary>
+        /// Initializes GRPC C Core library.
+        /// </summary>
+        private static void GrpcInit()
+        {
+            grpc_init();
+            threadPool.Start();
+            // TODO: use proper logging here
+            Console.WriteLine("GRPC initialized.");
+        }
+
+        /// <summary>
+        /// Shutdown GRPC C Core library.
+        /// </summary>
+        private static void GrpcShutdown()
+        {
+            threadPool.Stop();
+            grpc_shutdown();
+
+            // TODO: use proper logging here
+            Console.WriteLine("GRPC shutdown.");
+        }
+
+        internal static GrpcThreadPool ThreadPool
+        {
+            get
+            {
+                return threadPool;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Internal/AsyncCall.cs b/src/csharp/GrpcCore/Internal/AsyncCall.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e83ca0eaa9619bea73c4d0c7a011c376e3ff349e
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/AsyncCall.cs
@@ -0,0 +1,485 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// Listener for call events that can be delivered from a completion queue.
+    /// </summary>
+    internal interface ICallEventListener {
+    
+        void OnClientMetadata();
+
+        void OnRead(byte[] payload);
+
+        void OnWriteAccepted(GRPCOpError error);
+
+        void OnFinishAccepted(GRPCOpError error);
+
+        // ignore the status on server
+        void OnFinished(Status status);
+    }
+
+    /// <summary>
+    /// Handle native call lifecycle and provides convenience methods.
+    /// </summary>
+    internal class AsyncCall<TWrite, TRead>: ICallEventListener, IDisposable
+    {
+        readonly Func<TWrite, byte[]> serializer;
+        readonly Func<byte[], TRead> deserializer;
+
+        // TODO: make sure the delegate doesn't get garbage collected while 
+        // native callbacks are in the completion queue.
+        readonly EventCallbackDelegate callbackHandler;
+
+        object myLock = new object();
+        bool disposed;
+        CallSafeHandle call;
+
+        bool started;
+        bool errorOccured;
+
+        bool cancelRequested;
+        bool halfcloseRequested;
+        bool halfclosed;
+        bool doneWithReading;
+        Nullable<Status> finishedStatus;
+
+        TaskCompletionSource<object> writeTcs;
+        TaskCompletionSource<TRead> readTcs;
+        TaskCompletionSource<object> halfcloseTcs = new TaskCompletionSource<object>();
+        TaskCompletionSource<Status> finishedTcs = new TaskCompletionSource<Status>();
+
+        IObserver<TRead> readObserver;
+
+        public AsyncCall(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer)
+        {
+            this.serializer = serializer;
+            this.deserializer = deserializer;
+            this.callbackHandler = HandleEvent;
+        }
+
+        public Task WriteAsync(TWrite msg)
+        {
+            return StartWrite(msg, false).Task;
+        }
+
+        public Task WritesCompletedAsync()
+        {
+            WritesDone();
+            return halfcloseTcs.Task;
+        }
+
+        public Task WriteStatusAsync(Status status)
+        {
+            WriteStatus(status);
+            return halfcloseTcs.Task;
+        }
+
+        public Task<TRead> ReadAsync()
+        {
+            return StartRead().Task;
+        }
+
+        public Task<Status> Finished
+        {
+            get
+            {
+                return finishedTcs.Task;
+            }
+        }
+
+        /// <summary>
+        /// Initiates reading to given observer.
+        /// </summary>
+        public void StartReadingToStream(IObserver<TRead> readObserver) {
+            lock (myLock)
+            {
+                CheckStarted();
+                if (this.readObserver != null)
+                {
+                    throw new InvalidOperationException("Already registered an observer.");
+                }
+                this.readObserver = readObserver;
+                StartRead();
+            }
+        }
+
+        public void Initialize(Channel channel, String methodName) {
+            lock (myLock)
+            {
+               this.call = CallSafeHandle.Create(channel.Handle, methodName, channel.Target, Timespec.InfFuture);
+            }
+        }
+
+        public void InitializeServer(CallSafeHandle call)
+        {
+            lock(myLock)
+            {
+                this.call = call;
+            }
+        }
+
+        // Client only
+        public void Start(bool buffered, CompletionQueueSafeHandle cq)
+        {
+            lock (myLock)
+            {
+                if (started)
+                {
+                    throw new InvalidOperationException("Already started.");
+                }
+
+                call.Invoke(cq, buffered, callbackHandler, callbackHandler);
+                started = true;
+            }
+        }
+
+        // Server only
+        public void Accept(CompletionQueueSafeHandle cq)
+        {
+            lock (myLock)
+            {
+                if (started)
+                {
+                    throw new InvalidOperationException("Already started.");
+                }
+
+                call.ServerAccept(cq, callbackHandler);
+                call.ServerEndInitialMetadata(0);
+                started = true;
+            }
+        }
+
+        public TaskCompletionSource<object> StartWrite(TWrite msg, bool buffered)
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+                CheckCancelNotRequested();
+               
+                if (halfcloseRequested || halfclosed)
+                {
+                    throw new InvalidOperationException("Already halfclosed.");
+                }
+
+                if (writeTcs != null)
+                {
+                    throw new InvalidOperationException("Only one write can be pending at a time");
+                }
+
+                // TODO: wrap serialization...
+                byte[] payload = serializer(msg);
+               
+                call.StartWrite(payload, buffered, callbackHandler);
+                writeTcs = new TaskCompletionSource<object>();
+                return writeTcs;
+            }
+        }
+
+        // client only
+        public void WritesDone()
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+                CheckCancelNotRequested();
+
+                if (halfcloseRequested || halfclosed)
+                {
+                    throw new InvalidOperationException("Already halfclosed.");
+                }
+
+                call.WritesDone(callbackHandler);
+                halfcloseRequested = true;
+            }
+        }
+
+        // server only
+        public void WriteStatus(Status status)
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+                CheckCancelNotRequested();
+
+                if (halfcloseRequested || halfclosed)
+                {
+                    throw new InvalidOperationException("Already halfclosed.");
+                }
+
+                call.StartWriteStatus(status, callbackHandler);
+                halfcloseRequested = true;
+            }
+        }
+
+        public TaskCompletionSource<TRead> StartRead()
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+                CheckNoError();
+
+                // TODO: add check for not cancelled?
+
+                if (doneWithReading)
+                {
+                    throw new InvalidOperationException("Already read the last message.");
+                }
+
+                if (readTcs != null)
+                {
+                    throw new InvalidOperationException("Only one read can be pending at a time");
+                }
+
+                call.StartRead(callbackHandler);
+
+                readTcs = new TaskCompletionSource<TRead>();
+                return readTcs;
+            }
+        }
+
+        public void Cancel()
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+
+                cancelRequested = true;
+            }
+            // grpc_call_cancel is threadsafe
+            call.Cancel();
+        }
+
+        public void CancelWithStatus(Status status)
+        {
+            lock (myLock)
+            {
+                CheckStarted();
+                CheckNotFinished();
+
+                cancelRequested = true;
+            }
+            // grpc_call_cancel_with_status is threadsafe
+            call.CancelWithStatus(status);
+        }
+       
+        public void OnClientMetadata()
+        {
+            // TODO: implement....
+        }
+
+        public void OnRead(byte[] payload)
+        {
+            TaskCompletionSource<TRead> oldTcs = null;
+            IObserver<TRead> observer = null;
+            lock (myLock)
+            {
+                oldTcs = readTcs;
+                readTcs = null;
+                if (payload == null)
+                {
+                    doneWithReading = true;
+                }
+                observer = readObserver;
+            }
+
+            // TODO: wrap deserialization...
+            TRead msg = payload != null ? deserializer(payload) : default(TRead);
+
+            oldTcs.SetResult(msg);
+
+            // TODO: make sure we deliver reads in the right order.
+
+            if (observer != null)
+            {
+                if (payload != null)
+                {
+                    // TODO: wrap to handle exceptions
+                    observer.OnNext(msg);
+
+                    // start a new read
+                    StartRead();
+                }
+                else
+                {
+                    // TODO: wrap to handle exceptions;
+                    observer.OnCompleted();
+                }
+
+            }
+        }
+
+        public void OnWriteAccepted(GRPCOpError error)
+        {
+            TaskCompletionSource<object> oldTcs = null;
+            lock (myLock)
+            {
+                UpdateErrorOccured(error);
+                oldTcs = writeTcs;
+                writeTcs = null;
+            }
+
+            if (errorOccured)
+            {
+                // TODO: use the right type of exception...
+                oldTcs.SetException(new Exception("Write failed"));
+            }
+            else
+            {
+                // TODO: where does the continuation run?
+                oldTcs.SetResult(null);
+            }
+        }
+
+        public void OnFinishAccepted(GRPCOpError error)
+        {
+            lock (myLock)
+            {
+                UpdateErrorOccured(error);
+                halfclosed = true;
+            }
+
+            if (errorOccured)
+            {
+                halfcloseTcs.SetException(new Exception("Halfclose failed"));
+
+            }
+            else
+            {
+                halfcloseTcs.SetResult(null);
+            }
+
+        }
+
+        public void OnFinished(Status status)
+        {
+            lock (myLock)
+            {
+                finishedStatus = status;
+
+                DisposeResourcesIfNeeded();
+            }
+            finishedTcs.SetResult(status);
+
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!disposed)
+            {
+                if (disposing)
+                {
+                    if (call != null)
+                    {
+                        call.Dispose();
+                    }
+                } 
+                disposed = true;
+            }
+        }
+
+        private void UpdateErrorOccured(GRPCOpError error)
+        {
+            if (error == GRPCOpError.GRPC_OP_ERROR)
+            {
+                errorOccured = true;
+            }
+        }
+
+        private void CheckStarted()
+        {
+            if (!started)
+            {
+                throw new InvalidOperationException("Call not started");
+            }
+        }
+
+        private void CheckNoError()
+        {
+            if (errorOccured)
+            {
+                throw new InvalidOperationException("Error occured when processing call.");
+            }
+        }
+
+        private void CheckNotFinished()
+        {
+            if (finishedStatus.HasValue)
+            {
+                throw new InvalidOperationException("Already finished.");
+            }
+        }
+
+        private void CheckCancelNotRequested()
+        {
+            if (cancelRequested)
+            {
+                throw new InvalidOperationException("Cancel has been requested.");
+            }
+        }
+
+        private void DisposeResourcesIfNeeded()
+        {
+            if (call != null && started && finishedStatus.HasValue)
+            {
+                // TODO: should we also wait for all the pending events to finish?
+
+                call.Dispose();
+            }
+        }
+
+        private void HandleEvent(IntPtr eventPtr) {
+            try {
+                var ev = new EventSafeHandleNotOwned(eventPtr);
+                switch (ev.GetCompletionType())
+                {
+                case GRPCCompletionType.GRPC_CLIENT_METADATA_READ:
+                    OnClientMetadata();
+                    break;
+
+                case GRPCCompletionType.GRPC_READ:
+                    byte[] payload = ev.GetReadData();
+                    OnRead(payload);
+                    break;
+
+                case GRPCCompletionType.GRPC_WRITE_ACCEPTED:
+                    OnWriteAccepted(ev.GetWriteAccepted());
+                    break;
+
+                case GRPCCompletionType.GRPC_FINISH_ACCEPTED:
+                    OnFinishAccepted(ev.GetFinishAccepted());
+                    break;
+
+                case GRPCCompletionType.GRPC_FINISHED:
+                    OnFinished(ev.GetFinished());
+                    break;
+
+                default:
+                    throw new ArgumentException("Unexpected completion type");
+                }
+            } catch(Exception e) {
+                Console.WriteLine("Caught exception in a native handler: " + e);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/CallSafeHandle.cs b/src/csharp/GrpcCore/Internal/CallSafeHandle.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6c9c58a4c3f327f93f376f4790f433da5d469cd6
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/CallSafeHandle.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using Google.GRPC.Core;
+
+namespace Google.GRPC.Core.Internal
+{
+    // TODO: we need to make sure that the delegates are not collected before invoked.
+    internal delegate void EventCallbackDelegate(IntPtr eventPtr);
+
+    /// <summary>
+    /// grpc_call from <grpc/grpc.h>
+    /// </summary>
+	internal class CallSafeHandle : SafeHandleZeroIsInvalid
+	{
+        const UInt32 GRPC_WRITE_BUFFER_HINT = 1;
+
+        [DllImport("libgrpc.so")]
+        static extern CallSafeHandle grpc_channel_create_call_old(ChannelSafeHandle channel, string method, string host, Timespec deadline);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_add_metadata(CallSafeHandle call, IntPtr metadata, UInt32 flags);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_invoke_old(CallSafeHandle call, CompletionQueueSafeHandle cq, IntPtr metadataReadTag, IntPtr finishedTag, UInt32 flags);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_invoke_old")]
+        static extern GRPCCallError grpc_call_invoke_old_CALLBACK(CallSafeHandle call, CompletionQueueSafeHandle cq,
+                                                              [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate metadataReadCallback, 
+                                                              [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate finishedCallback, 
+                                                              UInt32 flags);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_server_accept_old(CallSafeHandle call, CompletionQueueSafeHandle completionQueue, IntPtr finishedTag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_server_accept_old")]
+        static extern GRPCCallError grpc_call_server_accept_old_CALLBACK(CallSafeHandle call, CompletionQueueSafeHandle completionQueue, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate finishedCallback);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_server_end_initial_metadata_old(CallSafeHandle call, UInt32 flags);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_cancel(CallSafeHandle call);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_cancel_with_status(CallSafeHandle call, StatusCode status, string description);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_start_write_status_old(CallSafeHandle call, StatusCode statusCode, string statusMessage, IntPtr tag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_start_write_status_old")]
+        static extern GRPCCallError grpc_call_start_write_status_old_CALLBACK(CallSafeHandle call, StatusCode statusCode, string statusMessage, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_writes_done_old(CallSafeHandle call, IntPtr tag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_writes_done_old")]
+        static extern GRPCCallError grpc_call_writes_done_old_CALLBACK(CallSafeHandle call, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc.so")]
+        static extern GRPCCallError grpc_call_start_read_old(CallSafeHandle call, IntPtr tag);
+
+        [DllImport("libgrpc.so", EntryPoint = "grpc_call_start_read_old")]
+        static extern GRPCCallError grpc_call_start_read_old_CALLBACK(CallSafeHandle call, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern void grpc_call_start_write_from_copied_buffer(CallSafeHandle call,
+                                                                    byte[] buffer, UIntPtr length,
+                                                                    IntPtr tag, UInt32 flags);
+
+        [DllImport("libgrpc_csharp_ext.so", EntryPoint = "grpc_call_start_write_from_copied_buffer")]
+        static extern void grpc_call_start_write_from_copied_buffer_CALLBACK(CallSafeHandle call,
+                                                                             byte[] buffer, UIntPtr length,
+                                                                             [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback,
+                                                                             UInt32 flags);
+
+		[DllImport("libgrpc.so")]
+		static extern void grpc_call_destroy(IntPtr call);
+
+        private CallSafeHandle()
+        {
+        }
+
+        /// <summary>
+        /// Creates a client call.
+        /// </summary>
+        public static CallSafeHandle Create(ChannelSafeHandle channel, string method, string host, Timespec deadline)
+        {
+            return grpc_channel_create_call_old(channel, method, host, deadline);
+        }
+
+        public void Invoke(CompletionQueueSafeHandle cq, IntPtr metadataReadTag, IntPtr finishedTag, bool buffered)
+        {   
+            AssertCallOk(grpc_call_invoke_old(this, cq, metadataReadTag, finishedTag, GetFlags(buffered)));
+        }
+
+        public void Invoke(CompletionQueueSafeHandle cq, bool buffered, EventCallbackDelegate metadataReadCallback, EventCallbackDelegate finishedCallback)
+        {   
+            AssertCallOk(grpc_call_invoke_old_CALLBACK(this, cq, metadataReadCallback, finishedCallback, GetFlags(buffered)));
+        }
+
+        public void ServerAccept(CompletionQueueSafeHandle cq, IntPtr finishedTag)
+        {
+            AssertCallOk(grpc_call_server_accept_old(this, cq, finishedTag));
+        }
+
+        public void ServerAccept(CompletionQueueSafeHandle cq, EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_server_accept_old_CALLBACK(this, cq, callback));
+        }
+
+        public void ServerEndInitialMetadata(UInt32 flags)
+        {
+            AssertCallOk(grpc_call_server_end_initial_metadata_old(this, flags));
+        }
+
+        public void StartWrite(byte[] payload, IntPtr tag, bool buffered)
+        {
+            grpc_call_start_write_from_copied_buffer(this, payload, new UIntPtr((ulong) payload.Length), tag, GetFlags(buffered));
+        }
+
+        public void StartWrite(byte[] payload, bool buffered, EventCallbackDelegate callback)
+        {
+            grpc_call_start_write_from_copied_buffer_CALLBACK(this, payload, new UIntPtr((ulong) payload.Length), callback, GetFlags(buffered));
+        }
+
+        public void StartWriteStatus(Status status, IntPtr tag)
+        {
+            AssertCallOk(grpc_call_start_write_status_old(this, status.StatusCode, status.Detail, tag));
+        }
+
+        public void StartWriteStatus(Status status, EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_start_write_status_old_CALLBACK(this, status.StatusCode, status.Detail, callback));
+        }
+
+        public void WritesDone(IntPtr tag)
+        {
+            AssertCallOk(grpc_call_writes_done_old(this, tag));
+        }
+
+        public void WritesDone(EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_writes_done_old_CALLBACK(this, callback));
+        }
+
+        public void StartRead(IntPtr tag)
+        {
+            AssertCallOk(grpc_call_start_read_old(this, tag));
+        }
+
+        public void StartRead(EventCallbackDelegate callback)
+        {
+            AssertCallOk(grpc_call_start_read_old_CALLBACK(this, callback));
+        }
+
+        public void Cancel()
+        {
+            AssertCallOk(grpc_call_cancel(this));
+        }
+
+        public void CancelWithStatus(Status status)
+        {
+            AssertCallOk(grpc_call_cancel_with_status(this, status.StatusCode, status.Detail));
+        }
+
+		protected override bool ReleaseHandle()
+		{
+			grpc_call_destroy(handle);
+			return true;
+		}
+
+        private static void AssertCallOk(GRPCCallError callError)
+        {
+            Trace.Assert(callError == GRPCCallError.GRPC_CALL_OK, "Status not GRPC_CALL_OK");
+        }
+
+        private static UInt32 GetFlags(bool buffered) {
+            return buffered ? 0 : GRPC_WRITE_BUFFER_HINT;
+        }
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs b/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3a09d8b1b641570cbc0ffcd09bacad97685adede
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_channel from <grpc/grpc.h>
+    /// </summary>
+	internal class ChannelSafeHandle : SafeHandleZeroIsInvalid
+	{
+        [DllImport("libgrpc.so")]
+        static extern ChannelSafeHandle grpc_channel_create(string target, IntPtr channelArgs);
+
+		[DllImport("libgrpc.so")]
+		static extern void grpc_channel_destroy(IntPtr channel);
+
+        private ChannelSafeHandle()
+        {
+        }
+
+        public static ChannelSafeHandle Create(string target, IntPtr channelArgs)
+        {
+            return grpc_channel_create(target, channelArgs);
+        }
+
+		protected override bool ReleaseHandle()
+		{
+			grpc_channel_destroy(handle);
+			return true;
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs b/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs
new file mode 100644
index 0000000000000000000000000000000000000000..73dd3edde333d23d7757a5fc9357e0f084365bd6
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_completion_queue from <grpc/grpc.h>
+    /// </summary>
+	internal class CompletionQueueSafeHandle : SafeHandleZeroIsInvalid
+	{
+        [DllImport("libgrpc.so")]
+        static extern CompletionQueueSafeHandle grpc_completion_queue_create();
+
+        [DllImport("libgrpc.so")]
+        static extern EventSafeHandle grpc_completion_queue_pluck(CompletionQueueSafeHandle cq, IntPtr tag, Timespec deadline);
+
+        [DllImport("libgrpc.so")]
+        static extern EventSafeHandle grpc_completion_queue_next(CompletionQueueSafeHandle cq, Timespec deadline);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_completion_queue_shutdown(CompletionQueueSafeHandle cq);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCCompletionType grpc_completion_queue_next_with_callback(CompletionQueueSafeHandle cq);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_completion_queue_destroy(IntPtr cq);
+
+        private CompletionQueueSafeHandle()
+        {
+        }
+
+        public static CompletionQueueSafeHandle Create()
+        {
+            return grpc_completion_queue_create();
+        }
+
+        public EventSafeHandle Next(Timespec deadline)
+        {
+            return grpc_completion_queue_next(this, deadline);
+        }
+
+        public GRPCCompletionType NextWithCallback()
+        {
+            return grpc_completion_queue_next_with_callback(this);
+        }
+
+        public EventSafeHandle Pluck(IntPtr tag, Timespec deadline)
+        {
+            return grpc_completion_queue_pluck(this, tag, deadline);
+        }
+
+        public void Shutdown()
+        {
+            grpc_completion_queue_shutdown(this);
+        }
+
+		protected override bool ReleaseHandle()
+        {
+            grpc_completion_queue_destroy(handle);
+			return true;
+		}
+	}
+}
+
diff --git a/src/csharp/GrpcCore/Internal/Enums.cs b/src/csharp/GrpcCore/Internal/Enums.cs
new file mode 100644
index 0000000000000000000000000000000000000000..46e3bca6ebebd4d5d6526c604d75485b1e0dac48
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/Enums.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// from grpc/grpc.h
+    /// </summary>
+    internal enum GRPCCallError
+    {
+        /* everything went ok */
+        GRPC_CALL_OK = 0,
+        /* something failed, we don't know what */
+        GRPC_CALL_ERROR,
+        /* this method is not available on the server */
+        GRPC_CALL_ERROR_NOT_ON_SERVER,
+        /* this method is not available on the client */
+        GRPC_CALL_ERROR_NOT_ON_CLIENT,
+        /* this method must be called before server_accept */
+        GRPC_CALL_ERROR_ALREADY_ACCEPTED,
+        /* this method must be called before invoke */
+        GRPC_CALL_ERROR_ALREADY_INVOKED,
+        /* this method must be called after invoke */
+        GRPC_CALL_ERROR_NOT_INVOKED,
+        /* this call is already finished
+     (writes_done or write_status has already been called) */
+        GRPC_CALL_ERROR_ALREADY_FINISHED,
+        /* there is already an outstanding read/write operation on the call */
+        GRPC_CALL_ERROR_TOO_MANY_OPERATIONS,
+        /* the flags value was illegal for this call */
+        GRPC_CALL_ERROR_INVALID_FLAGS
+    }
+
+    /// <summary>
+    /// grpc_completion_type from grpc/grpc.h
+    /// </summary>
+    internal enum GRPCCompletionType
+    {
+        GRPC_QUEUE_SHUTDOWN,
+        /* Shutting down */
+        GRPC_READ,
+        /* A read has completed */
+        GRPC_INVOKE_ACCEPTED,
+        /* An invoke call has been accepted by flow
+                                control */
+        GRPC_WRITE_ACCEPTED,
+        /* A write has been accepted by
+                                flow control */
+        GRPC_FINISH_ACCEPTED,
+        /* writes_done or write_status has been accepted */
+        GRPC_CLIENT_METADATA_READ,
+        /* The metadata array sent by server received at
+                                client */
+        GRPC_FINISHED,
+        /* An RPC has finished. The event contains status.
+                                On the server this will be OK or Cancelled. */
+        GRPC_SERVER_RPC_NEW,
+        /* A new RPC has arrived at the server */
+        GRPC_COMPLETION_DO_NOT_USE
+        /* must be last, forces users to include
+                                a default: case */
+    }
+
+    /// <summary>
+    /// grpc_op_error from grpc/grpc.h
+    /// </summary>
+    internal enum GRPCOpError
+    {
+        /* everything went ok */
+        GRPC_OP_OK = 0,
+        /* something failed, we don't know what */
+        GRPC_OP_ERROR
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Internal/Event.cs b/src/csharp/GrpcCore/Internal/Event.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7056005ba651cd3a39757c6aadb5e2446af72c56
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/Event.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Runtime.InteropServices;
+using Google.GRPC.Core;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_event from grpc/grpc.h
+    /// </summary>
+    internal class EventSafeHandle : SafeHandleZeroIsInvalid
+    {
+        [DllImport("libgrpc.so")]
+        static extern void grpc_event_finish(IntPtr ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCCompletionType grpc_event_type(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern CallSafeHandle grpc_event_call(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_write_accepted(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_finish_accepted(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern StatusCode grpc_event_finished_status(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_finished_details(EventSafeHandle ev);  // returns const char*
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_read_length(EventSafeHandle ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern void grpc_event_read_copy_to_buffer(EventSafeHandle ev, byte[] buffer, UIntPtr bufferLen);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_server_rpc_new_method(EventSafeHandle ev); // returns const char*
+
+        public GRPCCompletionType GetCompletionType()
+        {
+            return grpc_event_type(this);
+        }
+
+        public GRPCOpError GetWriteAccepted()
+        {
+            return grpc_event_write_accepted(this);
+        }
+
+        public GRPCOpError GetFinishAccepted()
+        {
+            return grpc_event_finish_accepted(this);
+        }
+
+        public Status GetFinished()
+        {
+            // TODO: can the native method return string directly?
+            string details = Marshal.PtrToStringAnsi(grpc_event_finished_details(this));
+            return new Status(grpc_event_finished_status(this), details);
+        }
+
+        public byte[] GetReadData()
+        {
+            IntPtr len = grpc_event_read_length(this);
+            if (len == new IntPtr(-1))
+            {
+                return null;
+            }
+            byte[] data = new byte[(int) len];
+            grpc_event_read_copy_to_buffer(this, data, new UIntPtr((ulong)data.Length));
+            return data;
+        }
+
+        public CallSafeHandle GetCall() {
+            return grpc_event_call(this);
+        }
+
+        public string GetServerRpcNewMethod() {
+            // TODO: can the native method return string directly?
+            return Marshal.PtrToStringAnsi(grpc_event_server_rpc_new_method(this));
+        }
+
+        //TODO: client_metadata_read event type
+
+        protected override bool ReleaseHandle()
+        {
+            grpc_event_finish(handle);
+            return true;
+        }
+    }
+
+    // TODO: this is basically c&p of EventSafeHandle. Unify!
+    /// <summary>
+    /// Not owned version of 
+    /// grpc_event from grpc/grpc.h
+    /// </summary>
+    internal class EventSafeHandleNotOwned : SafeHandleZeroIsInvalid
+    {
+        [DllImport("libgrpc.so")]
+        static extern void grpc_event_finish(IntPtr ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCCompletionType grpc_event_type(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern CallSafeHandle grpc_event_call(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_write_accepted(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern GRPCOpError grpc_event_finish_accepted(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern StatusCode grpc_event_finished_status(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_finished_details(EventSafeHandleNotOwned ev);  // returns const char*
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_read_length(EventSafeHandleNotOwned ev);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern void grpc_event_read_copy_to_buffer(EventSafeHandleNotOwned ev, byte[] buffer, UIntPtr bufferLen);
+
+        [DllImport("libgrpc_csharp_ext.so")]
+        static extern IntPtr grpc_event_server_rpc_new_method(EventSafeHandleNotOwned ev); // returns const char*
+
+        public EventSafeHandleNotOwned() : base(false)
+        {
+        }
+
+        public EventSafeHandleNotOwned(IntPtr handle) : base(false)
+        {
+            SetHandle(handle);
+        }
+
+        public GRPCCompletionType GetCompletionType()
+        {
+            return grpc_event_type(this);
+        }
+
+        public GRPCOpError GetWriteAccepted()
+        {
+            return grpc_event_write_accepted(this);
+        }
+
+        public GRPCOpError GetFinishAccepted()
+        {
+            return grpc_event_finish_accepted(this);
+        }
+
+        public Status GetFinished()
+        {
+            // TODO: can the native method return string directly?
+            string details = Marshal.PtrToStringAnsi(grpc_event_finished_details(this));
+            return new Status(grpc_event_finished_status(this), details);
+        }
+
+        public byte[] GetReadData()
+        {
+            IntPtr len = grpc_event_read_length(this);
+            if (len == new IntPtr(-1))
+            {
+                return null;
+            }
+            byte[] data = new byte[(int) len];
+            grpc_event_read_copy_to_buffer(this, data, new UIntPtr((ulong)data.Length));
+            return data;
+        }
+
+        public CallSafeHandle GetCall() {
+            return grpc_event_call(this);
+        }
+
+        public string GetServerRpcNewMethod() {
+            // TODO: can the native method return string directly?
+            return Marshal.PtrToStringAnsi(grpc_event_server_rpc_new_method(this));
+        }
+
+        //TODO: client_metadata_read event type
+
+        protected override bool ReleaseHandle()
+        {
+            grpc_event_finish(handle);
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs b/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1139e54a1d7d353a7ff7bb0964af1ffa07e10ba5
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs
@@ -0,0 +1,129 @@
+using System;
+using Google.GRPC.Core.Internal;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// Pool of threads polling on the same completion queue.
+    /// </summary>
+    internal class GrpcThreadPool
+    {
+        readonly object myLock = new object();
+        readonly List<Thread> threads = new List<Thread>();
+        readonly int poolSize;
+        readonly Action<EventSafeHandle> eventHandler;
+
+        CompletionQueueSafeHandle cq;
+
+        public GrpcThreadPool(int poolSize) {
+            this.poolSize = poolSize;
+        }
+
+        internal GrpcThreadPool(int poolSize, Action<EventSafeHandle> eventHandler) {
+            this.poolSize = poolSize;
+            this.eventHandler = eventHandler;
+        }
+
+        public void Start() {
+
+            lock (myLock)
+            {
+                if (cq != null)
+                {
+                    throw new InvalidOperationException("Already started.");
+                }
+
+                cq = CompletionQueueSafeHandle.Create();
+
+                for (int i = 0; i < poolSize; i++)
+                {
+                    threads.Add(CreateAndStartThread(i));
+                }
+            }
+        }
+
+        public void Stop() {
+
+            lock (myLock)
+            {
+                cq.Shutdown();
+
+                Console.WriteLine("Waiting for GPRC threads to finish.");
+                foreach (var thread in threads)
+                {
+                    thread.Join();
+                }
+
+                cq.Dispose();
+
+            }
+        }
+
+        internal CompletionQueueSafeHandle CompletionQueue
+        {
+            get
+            {
+                return cq;
+            }
+        }
+
+        private Thread CreateAndStartThread(int i) {
+            Action body;
+            if (eventHandler != null)
+            {
+                body = ThreadBodyWithHandler;
+            }
+            else
+            {
+                body = ThreadBodyNoHandler;
+            }
+            var thread = new Thread(new ThreadStart(body));
+            thread.IsBackground = false;
+            thread.Start();
+            if (eventHandler != null)
+            {
+                thread.Name = "grpc_server_newrpc " + i;
+            }
+            else
+            {
+                thread.Name = "grpc " + i;
+            }
+            return thread;
+        }
+
+        /// <summary>
+        /// Body of the polling thread.
+        /// </summary>
+        private void ThreadBodyNoHandler()
+        {
+            GRPCCompletionType completionType;
+            do
+            {
+                completionType = cq.NextWithCallback();
+            } while(completionType != GRPCCompletionType.GRPC_QUEUE_SHUTDOWN);
+            Console.WriteLine("Completion queue has shutdown successfully, thread " + Thread.CurrentThread.Name + " exiting.");
+        }
+
+        /// <summary>
+        /// Body of the polling thread.
+        /// </summary>
+        private void ThreadBodyWithHandler()
+        {
+            GRPCCompletionType completionType;
+            do
+            {
+                using (EventSafeHandle ev = cq.Next(Timespec.InfFuture)) {
+                    completionType = ev.GetCompletionType();
+                    eventHandler(ev);
+                }
+            } while(completionType != GRPCCompletionType.GRPC_QUEUE_SHUTDOWN);
+            Console.WriteLine("Completion queue has shutdown successfully, thread " + Thread.CurrentThread.Name + " exiting.");
+        }
+    }
+
+}
+
diff --git a/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs b/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a1252b881442cfc34e56be79c9dc6093d3387ec
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// Safe handle to wrap native objects.
+    /// </summary>
+    internal abstract class SafeHandleZeroIsInvalid : SafeHandle
+    {
+        public SafeHandleZeroIsInvalid() : base(IntPtr.Zero, true)
+        {
+        }
+
+        public SafeHandleZeroIsInvalid(bool ownsHandle) : base(IntPtr.Zero, ownsHandle)
+        {
+        }
+
+        public override bool IsInvalid
+        {
+            get
+            {
+                return handle == IntPtr.Zero;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0d38bce63e3a5c35db82c384a4884cc91f7be3eb
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Collections.Concurrent;
+
+namespace Google.GRPC.Core.Internal
+{
+    /// <summary>
+    /// grpc_server from grpc/grpc.h
+    /// </summary>
+    internal sealed class ServerSafeHandle : SafeHandleZeroIsInvalid
+    {
+        [DllImport("libgrpc.so", EntryPoint = "grpc_server_request_call_old")]
+        static extern GRPCCallError grpc_server_request_call_old_CALLBACK(ServerSafeHandle server, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback);
+
+        [DllImport("libgrpc.so")]
+        static extern ServerSafeHandle grpc_server_create(CompletionQueueSafeHandle cq, IntPtr args);
+
+        // TODO: check int representation size
+        [DllImport("libgrpc.so")]
+        static extern int grpc_server_add_http2_port(ServerSafeHandle server, string addr);
+
+        // TODO: check int representation size
+        [DllImport("libgrpc.so")]
+        static extern int grpc_server_add_secure_http2_port(ServerSafeHandle server, string addr);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_server_start(ServerSafeHandle server);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_server_shutdown(ServerSafeHandle server);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_server_shutdown_and_notify(ServerSafeHandle server, IntPtr tag);
+
+        [DllImport("libgrpc.so")]
+        static extern void grpc_server_destroy(IntPtr server);
+
+        private ServerSafeHandle()
+        {
+        }
+
+        public static ServerSafeHandle NewServer(CompletionQueueSafeHandle cq, IntPtr args)
+        {
+            // TODO: also grpc_secure_server_create...
+            return grpc_server_create(cq, args);
+        }
+
+        public int AddPort(string addr)
+        {
+            // TODO: also grpc_server_add_secure_http2_port...
+            return grpc_server_add_http2_port(this, addr);
+        }
+
+        public void Start()
+        {
+            grpc_server_start(this);
+        }
+
+        public void Shutdown()
+        {
+            grpc_server_shutdown(this);
+        }
+
+        public GRPCCallError RequestCall(EventCallbackDelegate callback)
+        {
+            return grpc_server_request_call_old_CALLBACK(this, callback);
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            grpc_server_destroy(handle);
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d483e53a2db1e43440903cfa10e2366d10896ad7
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs
@@ -0,0 +1,33 @@
+using System;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    internal class StreamingInputObserver<TWrite, TRead> : IObserver<TWrite>
+	{
+        readonly AsyncCall<TWrite, TRead> call;
+
+        public StreamingInputObserver(AsyncCall<TWrite, TRead> call)
+		{
+            this.call = call;
+		}
+
+		public void OnCompleted()
+		{
+            // TODO: how bad is the Wait here?
+            call.WritesCompletedAsync().Wait();
+		}
+
+		public void OnError(Exception error)
+		{
+			throw new InvalidOperationException("This should never be called.");
+		}
+
+		public void OnNext(TWrite value)
+		{
+            // TODO: how bad is the Wait here?
+            call.WriteAsync(value).Wait();
+		}
+	}
+}
+
diff --git a/src/csharp/GrpcCore/Internal/Timespec.cs b/src/csharp/GrpcCore/Internal/Timespec.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8ffaf70bbfbf149ba3f98975616bea583b0ea2c7
--- /dev/null
+++ b/src/csharp/GrpcCore/Internal/Timespec.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Google.GRPC.Core.Internal
+{
+	/// <summary>
+	/// gpr_timespec from grpc/support/time.h
+	/// </summary>
+	[StructLayout(LayoutKind.Sequential)]
+	internal struct Timespec
+	{
+        const int nanosPerSecond = 1000 * 1000 * 1000;
+        const int nanosPerTick = 100;
+
+        [DllImport("libgpr.so")]
+        static extern Timespec gpr_now();
+
+		// TODO: this only works on 64bit linux, can we autoselect the right size of ints?
+		// perhaps using IntPtr would work.
+		public System.Int64 tv_sec;
+		public System.Int64 tv_nsec;
+
+		/// <summary>
+		/// Timespec a long time in the future.
+		/// </summary>
+		public static Timespec InfFuture
+		{
+			get
+			{
+				// TODO: set correct value based on the length of the struct
+				return new Timespec { tv_sec = Int32.MaxValue, tv_nsec = 0 };
+			}
+		}
+
+        public static Timespec Now
+        {
+            get
+            {
+                return gpr_now();
+            }
+        }
+
+        /// <summary>
+        /// Creates a GPR deadline from current instant and given timeout.
+        /// </summary>
+        /// <returns>The from timeout.</returns>
+        public static Timespec DeadlineFromTimeout(TimeSpan timeout) {
+            if (timeout == Timeout.InfiniteTimeSpan)
+            {
+                return Timespec.InfFuture;
+            }
+            return Timespec.Now.Add(timeout);
+        }
+
+        public Timespec Add(TimeSpan timeSpan) {
+            long nanos = tv_nsec + (timeSpan.Ticks % TimeSpan.TicksPerSecond) * nanosPerTick;
+            long overflow_sec = (nanos > nanosPerSecond) ? 1 : 0;
+
+            Timespec result;
+            result.tv_nsec = nanos % nanosPerSecond;
+            result.tv_sec = tv_sec + (timeSpan.Ticks / TimeSpan.TicksPerSecond) + overflow_sec; 
+            return result;
+        }
+	}
+}
+
diff --git a/src/csharp/GrpcCore/Properties/AssemblyInfo.cs b/src/csharp/GrpcCore/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..74aba25767844a4c7f8b5df55c67ab59b8b9c38a
--- /dev/null
+++ b/src/csharp/GrpcCore/Properties/AssemblyInfo.cs
@@ -0,0 +1,24 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle ("GrpcCore")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("jtattermusch")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion ("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+[assembly: InternalsVisibleTo("GrpcCoreTests")]
+
diff --git a/src/csharp/GrpcCore/RpcException.cs b/src/csharp/GrpcCore/RpcException.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8811c3a7c75513382f805a88b8c20623aab79d4f
--- /dev/null
+++ b/src/csharp/GrpcCore/RpcException.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Google.GRPC.Core
+{
+    public class RpcException : Exception
+    {
+        private readonly Status status;
+
+        public RpcException(Status status)
+        {
+            this.status = status;
+        }
+
+        public RpcException(Status status, string message) : base(message)
+        {
+            this.status = status;
+        }
+
+        public Status Status {
+            get
+            {
+                return status;
+            }
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCore/Server.cs b/src/csharp/GrpcCore/Server.cs
new file mode 100644
index 0000000000000000000000000000000000000000..68da1a8300813a98ad76aef285e60850295f3080
--- /dev/null
+++ b/src/csharp/GrpcCore/Server.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Collections.Concurrent;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core
+{
+    /// <summary>
+    /// Server is implemented only to be able to do
+    /// in-process testing.
+    /// </summary>
+    public class Server
+    {
+        // TODO: make sure the delegate doesn't get garbage collected while 
+        // native callbacks are in the completion queue.
+        readonly EventCallbackDelegate newRpcHandler;
+
+        readonly BlockingCollection<NewRpcInfo> newRpcQueue = new BlockingCollection<NewRpcInfo>();
+        readonly ServerSafeHandle handle;
+
+        static Server() {
+            GrpcEnvironment.EnsureInitialized();
+        }
+
+        public Server()
+        {
+            // TODO: what is the tag for server shutdown?
+            this.handle = ServerSafeHandle.NewServer(GetCompletionQueue(), IntPtr.Zero);
+            this.newRpcHandler = HandleNewRpc;
+        }
+
+        public int AddPort(string addr) {
+            return handle.AddPort(addr);
+        }
+
+        public void Start()
+        {
+            handle.Start();
+        }
+
+        public void RunRpc()
+        {
+            AllowOneRpc();
+         
+            try {
+            var rpcInfo = newRpcQueue.Take();
+
+            Console.WriteLine("Server received RPC " + rpcInfo.Method);
+
+            AsyncCall<byte[], byte[]> asyncCall = new AsyncCall<byte[], byte[]>(
+                (payload) => payload, (payload) => payload);
+
+            asyncCall.InitializeServer(rpcInfo.Call);
+
+            asyncCall.Accept(GetCompletionQueue());
+
+            while(true) {
+                byte[] payload = asyncCall.ReadAsync().Result;
+                if (payload == null)
+                {
+                    break;
+                }
+            }
+
+            asyncCall.WriteAsync(new byte[] { }).Wait();
+
+            // TODO: what should be the details?
+            asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait();
+
+            asyncCall.Finished.Wait();
+            } catch(Exception e) {
+                Console.WriteLine("Exception while handling RPC: " + e);
+            }
+        }
+
+        // TODO: implement disposal properly...
+        public void Shutdown() {
+            handle.Shutdown();
+
+
+            //handle.Dispose();
+        }
+
+        private void AllowOneRpc()
+        {
+            AssertCallOk(handle.RequestCall(newRpcHandler));
+        }
+
+        private void HandleNewRpc(IntPtr eventPtr)
+        {
+            try
+            {
+                var ev = new EventSafeHandleNotOwned(eventPtr);
+                newRpcQueue.Add(new NewRpcInfo(ev.GetCall(), ev.GetServerRpcNewMethod()));
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("Caught exception in a native handler: " + e);
+            }
+        }
+
+        private static void AssertCallOk(GRPCCallError callError)
+        {
+            Trace.Assert(callError == GRPCCallError.GRPC_CALL_OK, "Status not GRPC_CALL_OK");
+        }
+
+        private static CompletionQueueSafeHandle GetCompletionQueue()
+        {
+            return GrpcEnvironment.ThreadPool.CompletionQueue;
+        }
+
+        private struct NewRpcInfo
+        {
+            private CallSafeHandle call;
+            private string method;
+
+            public NewRpcInfo(CallSafeHandle call, string method)
+            {
+                this.call = call;
+                this.method = method;
+            }
+
+            public CallSafeHandle Call
+            {
+                get
+                {
+                    return this.call;
+                }
+            }
+
+            public string Method
+            {
+                get
+                {
+                    return this.method;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/Status.cs b/src/csharp/GrpcCore/Status.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f1212f8d672cf8ccd02a113560b07b5fffe17325
--- /dev/null
+++ b/src/csharp/GrpcCore/Status.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Google.GRPC.Core
+{
+	/// <summary>
+	/// Represents RPC result.
+	/// </summary>
+	public struct Status
+	{
+		readonly StatusCode statusCode;
+		readonly string detail;
+
+		public Status(StatusCode statusCode, string detail)
+		{
+			this.statusCode = statusCode;
+			this.detail = detail;
+		}
+
+		public StatusCode StatusCode
+		{
+			get
+			{
+				return statusCode;
+			}
+		}
+
+		public string Detail
+		{
+			get
+			{
+				return detail;
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/csharp/GrpcCore/StatusCode.cs b/src/csharp/GrpcCore/StatusCode.cs
new file mode 100644
index 0000000000000000000000000000000000000000..80fc8bd5815d75d4926997d953505ff213810500
--- /dev/null
+++ b/src/csharp/GrpcCore/StatusCode.cs
@@ -0,0 +1,150 @@
+using System;
+
+namespace Google.GRPC.Core
+{
+    // TODO: element names should changed to comply with C# naming conventions.
+    /// <summary>
+    /// grpc_status_code from grpc/status.h
+    /// </summary>
+    public enum StatusCode
+    {
+        /* Not an error; returned on success
+
+     HTTP Mapping: 200 OK */
+        GRPC_STATUS_OK = 0,
+        /* The operation was cancelled (typically by the caller).
+
+     HTTP Mapping: 499 Client Closed Request */
+        GRPC_STATUS_CANCELLED = 1,
+        /* Unknown error.  An example of where this error may be returned is
+     if a Status value received from another address space belongs to
+     an error-space that is not known in this address space.  Also
+     errors raised by APIs that do not return enough error information
+     may be converted to this error.
+
+     HTTP Mapping: 500 Internal Server Error */
+        GRPC_STATUS_UNKNOWN = 2,
+        /* Client specified an invalid argument.  Note that this differs
+     from FAILED_PRECONDITION.  INVALID_ARGUMENT indicates arguments
+     that are problematic regardless of the state of the system
+     (e.g., a malformed file name).
+
+     HTTP Mapping: 400 Bad Request */
+        GRPC_STATUS_INVALID_ARGUMENT = 3,
+        /* Deadline expired before operation could complete.  For operations
+     that change the state of the system, this error may be returned
+     even if the operation has completed successfully.  For example, a
+     successful response from a server could have been delayed long
+     enough for the deadline to expire.
+
+     HTTP Mapping: 504 Gateway Timeout */
+        GRPC_STATUS_DEADLINE_EXCEEDED = 4,
+        /* Some requested entity (e.g., file or directory) was not found.
+
+     HTTP Mapping: 404 Not Found */
+        GRPC_STATUS_NOT_FOUND = 5,
+        /* Some entity that we attempted to create (e.g., file or directory)
+     already exists.
+
+     HTTP Mapping: 409 Conflict */
+        GRPC_STATUS_ALREADY_EXISTS = 6,
+        /* The caller does not have permission to execute the specified
+     operation.  PERMISSION_DENIED must not be used for rejections
+     caused by exhausting some resource (use RESOURCE_EXHAUSTED
+     instead for those errors).  PERMISSION_DENIED must not be
+     used if the caller can not be identified (use UNAUTHENTICATED
+     instead for those errors).
+
+     HTTP Mapping: 403 Forbidden */
+        GRPC_STATUS_PERMISSION_DENIED = 7,
+        /* The request does not have valid authentication credentials for the
+     operation.
+
+     HTTP Mapping: 401 Unauthorized */
+        GRPC_STATUS_UNAUTHENTICATED = 16,
+        /* Some resource has been exhausted, perhaps a per-user quota, or
+     perhaps the entire file system is out of space.
+
+     HTTP Mapping: 429 Too Many Requests */
+        GRPC_STATUS_RESOURCE_EXHAUSTED = 8,
+        /* Operation was rejected because the system is not in a state
+     required for the operation's execution.  For example, directory
+     to be deleted may be non-empty, an rmdir operation is applied to
+     a non-directory, etc.
+
+     A litmus test that may help a service implementor in deciding
+     between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
+      (a) Use UNAVAILABLE if the client can retry just the failing call.
+      (b) Use ABORTED if the client should retry at a higher-level
+          (e.g., restarting a read-modify-write sequence).
+      (c) Use FAILED_PRECONDITION if the client should not retry until
+          the system state has been explicitly fixed.  E.g., if an "rmdir"
+          fails because the directory is non-empty, FAILED_PRECONDITION
+          should be returned since the client should not retry unless
+          they have first fixed up the directory by deleting files from it.
+      (d) Use FAILED_PRECONDITION if the client performs conditional
+          REST Get/Update/Delete on a resource and the resource on the
+          server does not match the condition. E.g., conflicting
+          read-modify-write on the same resource.
+
+     HTTP Mapping: 400 Bad Request
+
+     NOTE: HTTP spec says 412 Precondition Failed should only be used if
+     the request contains Etag related headers. So if the server does see
+     Etag related headers in the request, it may choose to return 412
+     instead of 400 for this error code. */
+        GRPC_STATUS_FAILED_PRECONDITION = 9,
+        /* The operation was aborted, typically due to a concurrency issue
+     like sequencer check failures, transaction aborts, etc.
+
+     See litmus test above for deciding between FAILED_PRECONDITION,
+     ABORTED, and UNAVAILABLE.
+
+     HTTP Mapping: 409 Conflict */
+        GRPC_STATUS_ABORTED = 10,
+        /* Operation was attempted past the valid range.  E.g., seeking or
+     reading past end of file.
+
+     Unlike INVALID_ARGUMENT, this error indicates a problem that may
+     be fixed if the system state changes. For example, a 32-bit file
+     system will generate INVALID_ARGUMENT if asked to read at an
+     offset that is not in the range [0,2^32-1], but it will generate
+     OUT_OF_RANGE if asked to read from an offset past the current
+     file size.
+
+     There is a fair bit of overlap between FAILED_PRECONDITION and
+     OUT_OF_RANGE.  We recommend using OUT_OF_RANGE (the more specific
+     error) when it applies so that callers who are iterating through
+     a space can easily look for an OUT_OF_RANGE error to detect when
+     they are done.
+
+     HTTP Mapping: 400 Bad Request */
+        GRPC_STATUS_OUT_OF_RANGE = 11,
+        /* Operation is not implemented or not supported/enabled in this service.
+
+     HTTP Mapping: 501 Not Implemented */
+        GRPC_STATUS_UNIMPLEMENTED = 12,
+        /* Internal errors.  Means some invariants expected by underlying
+     system has been broken.  If you see one of these errors,
+     something is very broken.
+
+     HTTP Mapping: 500 Internal Server Error */
+        GRPC_STATUS_INTERNAL = 13,
+        /* The service is currently unavailable.  This is a most likely a
+     transient condition and may be corrected by retrying with
+     a backoff.
+
+     See litmus test above for deciding between FAILED_PRECONDITION,
+     ABORTED, and UNAVAILABLE.
+
+     HTTP Mapping: 503 Service Unavailable */
+        GRPC_STATUS_UNAVAILABLE = 14,
+        /* Unrecoverable data loss or corruption.
+
+     HTTP Mapping: 500 Internal Server Error */
+        GRPC_STATUS_DATA_LOSS = 15,
+        /* Force users to include a default branch: */
+        GRPC_STATUS__DO_NOT_USE = -1
+    }
+}
+
diff --git a/src/csharp/GrpcCoreTests/.gitignore b/src/csharp/GrpcCoreTests/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2cc8cca52d01ba50f6398e7cb7a147161dad578c
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/.gitignore
@@ -0,0 +1,2 @@
+test-results
+bin
diff --git a/src/csharp/GrpcCoreTests/ClientServerTest.cs b/src/csharp/GrpcCoreTests/ClientServerTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..823ee942882993a521df18f95608bf48f2bc2fd5
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/ClientServerTest.cs
@@ -0,0 +1,48 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core.Internal;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Google.GRPC.Core.Tests
+{
+    public class ClientServerTest
+    {
+        string request = "REQUEST";
+        string serverAddr = "localhost:" + Utils.PickUnusedPort();
+
+        [Test]
+        public void EmptyCall()
+        {
+            Server server = new Server();
+            server.AddPort(serverAddr);
+            server.Start();
+
+            Task.Factory.StartNew(
+                () => {
+                    server.RunRpc();
+                }
+            );
+
+            using (Channel channel = new Channel(serverAddr))
+            {
+                CreateCall(channel);
+                string response = Calls.BlockingUnaryCall(CreateCall(channel), request, default(CancellationToken));
+                Console.WriteLine("Received response: " + response);
+            }
+         
+            server.Shutdown();
+
+            GrpcEnvironment.Shutdown();
+        }
+
+        private Call<string, string> CreateCall(Channel channel)
+        {
+            return new Call<string, string>("/tests.Test/EmptyCall",
+                                        (s) => System.Text.Encoding.ASCII.GetBytes(s), 
+                                        (b) => System.Text.Encoding.ASCII.GetString(b),
+                                        Timeout.InfiniteTimeSpan, channel);
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..3de0f585cda057e42d72301e09cfaf79234a627a
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{86EC5CB4-4EA2-40A2-8057-86542A0353BB}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>GrpcCoreTests</RootNamespace>
+    <AssemblyName>GrpcCoreTests</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="nunit.framework, Version=2.6.0.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77">
+      <Private>False</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ClientServerTest.cs" />
+    <Compile Include="ServerTest.cs" />
+    <Compile Include="Utils.cs" />
+    <Compile Include="GrpcEnvironmentTest.cs" />
+    <Compile Include="TimespecTest.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\GrpcCore\GrpcCore.csproj">
+      <Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project>
+      <Name>GrpcCore</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs b/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..136878d76eb6ba061e999e02261b091269c58235
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs
@@ -0,0 +1,18 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core;
+using System.Threading;
+
+namespace Google.GRPC.Core.Tests
+{
+    public class GrpcEnvironmentTest
+    {
+        [Test]
+        public void InitializeAndShutdownGrpcEnvironment() {
+            GrpcEnvironment.EnsureInitialized();
+            Thread.Sleep(500);
+            Assert.IsNotNull(GrpcEnvironment.ThreadPool.CompletionQueue);
+            GrpcEnvironment.Shutdown();
+        }
+    }
+}
diff --git a/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs b/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..565b1e2bd65b326f5b243e88895df9e64039fc82
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle("GrpcCoreTests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("jtattermusch")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/src/csharp/GrpcCoreTests/ServerTest.cs b/src/csharp/GrpcCoreTests/ServerTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b34101bbf597e7baa50b1bf94bc5cfac8a5dbb04
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/ServerTest.cs
@@ -0,0 +1,21 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Tests
+{
+    public class ServerTest
+    {
+        [Test]
+        public void StartAndShutdownServer() {
+
+            Server server = new Server();
+            server.AddPort("localhost:" + Utils.PickUnusedPort());
+            server.Start();
+            server.Shutdown();
+
+            GrpcEnvironment.Shutdown();
+        }
+
+    }
+}
diff --git a/src/csharp/GrpcCoreTests/TestResult.xml b/src/csharp/GrpcCoreTests/TestResult.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a5a6abd7b958c05ca4d263d7053fbc62fd18cc66
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/TestResult.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!--This file represents the results of running a test suite-->
+<test-results name="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests/bin/Debug/GrpcCoreTests.dll" total="3" errors="0" failures="0" not-run="0" inconclusive="0" ignored="0" skipped="0" invalid="0" date="2015-01-29" time="19:40:47">
+  <environment nunit-version="2.6.0.0" clr-version="4.0.30319.17020" os-version="Unix 3.13.0.43" platform="Unix" cwd="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests" machine-name="jtattermusch.mtv.corp.google.com" user="jtattermusch" user-domain="jtattermusch.mtv.corp.google.com" />
+  <culture-info current-culture="en-US" current-uiculture="en-US" />
+  <test-suite type="Assembly" name="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests/bin/Debug/GrpcCoreTests.dll" executed="True" result="Success" success="True" time="0.172" asserts="0">
+    <results>
+      <test-suite type="Namespace" name="Google" executed="True" result="Success" success="True" time="0.166" asserts="0">
+        <results>
+          <test-suite type="Namespace" name="GRPC" executed="True" result="Success" success="True" time="0.166" asserts="0">
+            <results>
+              <test-suite type="Namespace" name="Core" executed="True" result="Success" success="True" time="0.166" asserts="0">
+                <results>
+                  <test-suite type="Namespace" name="Tests" executed="True" result="Success" success="True" time="0.166" asserts="0">
+                    <results>
+                      <test-suite type="TestFixture" name="CallsTest" executed="True" result="Success" success="True" time="0.009" asserts="0">
+                        <results>
+                          <test-case name="Google.GRPC.Core.Tests.CallsTest.Test1" executed="True" result="Success" success="True" time="0.004" asserts="0" />
+                        </results>
+                      </test-suite>
+                      <test-suite type="TestFixture" name="ClientServerTest" executed="True" result="Success" success="True" time="0.149" asserts="0">
+                        <results>
+                          <test-case name="Google.GRPC.Core.Tests.ClientServerTest.EmptyCall" executed="True" result="Success" success="True" time="0.111" asserts="0" />
+                        </results>
+                      </test-suite>
+                      <test-suite type="TestFixture" name="ServerTest" executed="True" result="Success" success="True" time="0.001" asserts="0">
+                        <results>
+                          <test-case name="Google.GRPC.Core.Tests.ServerTest.StartAndShutdownServer" executed="True" result="Success" success="True" time="0.001" asserts="0" />
+                        </results>
+                      </test-suite>
+                    </results>
+                  </test-suite>
+                </results>
+              </test-suite>
+            </results>
+          </test-suite>
+        </results>
+      </test-suite>
+    </results>
+  </test-suite>
+</test-results>
\ No newline at end of file
diff --git a/src/csharp/GrpcCoreTests/TimespecTest.cs b/src/csharp/GrpcCoreTests/TimespecTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..484bad7ca19a4b3953ada37e706f76d798187556
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/TimespecTest.cs
@@ -0,0 +1,43 @@
+using System;
+using NUnit.Framework;
+using Google.GRPC.Core.Internal;
+
+namespace Google.GRPC.Core.Internal.Tests
+{
+    public class TimespecTest
+    {
+        [Test]
+        public void Now()
+        {
+            var timespec = Timespec.Now;
+        }
+
+        [Test]
+        public void Add()
+        {
+            var t = new Timespec { tv_sec = 12345, tv_nsec = 123456789 };
+            var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10));
+            Assert.AreEqual(result.tv_sec, 12355);
+            Assert.AreEqual(result.tv_nsec, 123456789);
+        }
+
+        [Test]
+        public void Add_Nanos()
+        {
+            var t = new Timespec { tv_sec = 12345, tv_nsec = 123456789 };
+            var result = t.Add(TimeSpan.FromTicks(10));
+            Assert.AreEqual(result.tv_sec, 12345);
+            Assert.AreEqual(result.tv_nsec, 123456789 + 1000);
+        }
+
+        [Test]
+        public void Add_NanosOverflow()
+        {
+            var t = new Timespec { tv_sec = 12345, tv_nsec = 999999999 };
+            var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10 + 10));
+            Assert.AreEqual(result.tv_sec, 12356);
+            Assert.AreEqual(result.tv_nsec, 999);
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcCoreTests/Utils.cs b/src/csharp/GrpcCoreTests/Utils.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b0c0a7b6205ed5061c309120db377a561b017003
--- /dev/null
+++ b/src/csharp/GrpcCoreTests/Utils.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Google.GRPC.Core.Tests
+{
+    /// <summary>
+    /// Testing utils.
+    /// </summary>
+    public class Utils
+    {
+        static Random random = new Random();
+        // TODO: cleanup this code a bit
+        public static int PickUnusedPort()
+        {
+            int port;
+            do
+            {
+                port = random.Next(2000, 50000);
+
+            } while(!IsPortAvailable(port));
+            return port;
+        }
+        // TODO: cleanup this code a bit
+        public static bool IsPortAvailable(int port)
+        {
+            bool available = true;
+
+            TcpListener server = null;
+            try
+            {
+                IPAddress ipAddress = Dns.GetHostEntry("localhost").AddressList[0];
+                server = new TcpListener(ipAddress, port);
+                server.Start();
+            }
+            catch (Exception ex)
+            {
+                available = false;
+            }
+            finally
+            {
+                if (server != null)
+                {
+                    server.Stop();
+                }
+            }
+            return available;
+        }
+    }
+}
+
diff --git a/src/csharp/GrpcDemo/.gitignore b/src/csharp/GrpcDemo/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ba077a4031add5b3a04384f8b9cfc414efbf47dd
--- /dev/null
+++ b/src/csharp/GrpcDemo/.gitignore
@@ -0,0 +1 @@
+bin
diff --git a/src/csharp/GrpcDemo/GrpcDemo.csproj b/src/csharp/GrpcDemo/GrpcDemo.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..31ce7f133b3a69f427a8f88a45aa7a1861eac377
--- /dev/null
+++ b/src/csharp/GrpcDemo/GrpcDemo.csproj
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>GrpcDemo</RootNamespace>
+    <AssemblyName>GrpcDemo</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\GrpcApi\GrpcApi.csproj">
+      <Project>{7DC1433E-3225-42C7-B7EA-546D56E27A4B}</Project>
+      <Name>GrpcApi</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\GrpcCore\GrpcCore.csproj">
+      <Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project>
+      <Name>GrpcCore</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/csharp/GrpcDemo/Program.cs b/src/csharp/GrpcDemo/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..258762dbb99d39440eb3806a503a50b29def85dd
--- /dev/null
+++ b/src/csharp/GrpcDemo/Program.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.InteropServices;
+using Google.GRPC.Core;
+using System.Threading;
+using math;
+
+namespace Google.GRPC.Demo
+{
+	class MainClass
+    {
+		public static void Main (string[] args)
+		{
+			using (Channel channel = new Channel("127.0.0.1:23456"))
+			{
+				IMathServiceClient stub = new MathServiceClientStub(channel, Timeout.InfiniteTimeSpan);
+				Examples.DivExample(stub);
+
+                Examples.FibExample(stub);
+
+				Examples.SumExample(stub);
+
+				Examples.DivManyExample(stub);
+			}
+           
+            GrpcEnvironment.Shutdown();
+		}
+	}
+}
diff --git a/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs b/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b8e1406da7c6a90f72c8f0900fb5623398b097b7
--- /dev/null
+++ b/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle ("GrpcDemo")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("jtattermusch")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion ("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/src/csharp/README.md b/src/csharp/README.md
index 5b56303c14748609abc0242e12d5997305dbe4c3..75bfb26252caa6e3072e8bf62024ff0c9f78817b 100755
--- a/src/csharp/README.md
+++ b/src/csharp/README.md
@@ -19,4 +19,8 @@ CONTENTS
 
 - ext:
   The extension library that wraps C API to be more digestible by C#.
+- GrpcCore:
+  The main gRPC C# library.
+- GrpcApi:
+  API examples for math.proto.
 
diff --git a/src/csharp/lib/Google.ProtocolBuffers.dll b/src/csharp/lib/Google.ProtocolBuffers.dll
new file mode 100755
index 0000000000000000000000000000000000000000..ce2f466b243da0e875764856cd99c61ca9d98628
Binary files /dev/null and b/src/csharp/lib/Google.ProtocolBuffers.dll differ