diff --git a/BUILD b/BUILD
index df082e47787dc909e98f7b7c8a343db13d343a7f..35e8f444963d2e59fd5f510e7e252c21684e33a3 100644
--- a/BUILD
+++ b/BUILD
@@ -737,6 +737,8 @@ cc_library(
     "src/compiler/config.h",
     "src/compiler/cpp_generator.h",
     "src/compiler/cpp_generator_helpers.h",
+    "src/compiler/csharp_generator.h",
+    "src/compiler/csharp_generator_helpers.h",
     "src/compiler/generator_helpers.h",
     "src/compiler/objective_c_generator.h",
     "src/compiler/objective_c_generator_helpers.h",
@@ -746,6 +748,7 @@ cc_library(
     "src/compiler/ruby_generator_map-inl.h",
     "src/compiler/ruby_generator_string-inl.h",
     "src/compiler/cpp_generator.cc",
+    "src/compiler/csharp_generator.cc",
     "src/compiler/objective_c_generator.cc",
     "src/compiler/python_generator.cc",
     "src/compiler/ruby_generator.cc",
@@ -793,6 +796,18 @@ cc_binary(
 )
 
 
+cc_binary(
+  name = "grpc_csharp_plugin",
+  srcs = [
+    "src/compiler/csharp_plugin.cc",
+  ],
+  deps = [
+    "//external:protobuf_compiler",
+    ":grpc_plugin_support",
+  ],
+)
+
+
 cc_binary(
   name = "grpc_objective_c_plugin",
   srcs = [
diff --git a/Makefile b/Makefile
index 1942e75f651f8ac1d83e092cc928cb67a513c726..d646d85335102eb8deafd232f375cd3841048cd8 100644
--- a/Makefile
+++ b/Makefile
@@ -490,7 +490,7 @@ endif
 
 .SECONDARY = %.pb.h %.pb.cc
 
-PROTOC_PLUGINS = $(BINDIR)/$(CONFIG)/grpc_cpp_plugin $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin $(BINDIR)/$(CONFIG)/grpc_python_plugin $(BINDIR)/$(CONFIG)/grpc_ruby_plugin
+PROTOC_PLUGINS = $(BINDIR)/$(CONFIG)/grpc_cpp_plugin $(BINDIR)/$(CONFIG)/grpc_csharp_plugin $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin $(BINDIR)/$(CONFIG)/grpc_python_plugin $(BINDIR)/$(CONFIG)/grpc_ruby_plugin
 ifeq ($(DEP_MISSING),)
 all: static shared plugins
 dep_error:
@@ -675,6 +675,7 @@ end2end_test: $(BINDIR)/$(CONFIG)/end2end_test
 generic_end2end_test: $(BINDIR)/$(CONFIG)/generic_end2end_test
 grpc_cli: $(BINDIR)/$(CONFIG)/grpc_cli
 grpc_cpp_plugin: $(BINDIR)/$(CONFIG)/grpc_cpp_plugin
+grpc_csharp_plugin: $(BINDIR)/$(CONFIG)/grpc_csharp_plugin
 grpc_objective_c_plugin: $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin
 grpc_python_plugin: $(BINDIR)/$(CONFIG)/grpc_python_plugin
 grpc_ruby_plugin: $(BINDIR)/$(CONFIG)/grpc_ruby_plugin
@@ -2249,6 +2250,8 @@ else
 	$(Q) $(INSTALL) -d $(prefix)/bin
 	$(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_cpp_plugin $(prefix)/bin/grpc_cpp_plugin
 	$(Q) $(INSTALL) -d $(prefix)/bin
+	$(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_csharp_plugin $(prefix)/bin/grpc_csharp_plugin
+	$(Q) $(INSTALL) -d $(prefix)/bin
 	$(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin $(prefix)/bin/grpc_objective_c_plugin
 	$(Q) $(INSTALL) -d $(prefix)/bin
 	$(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_python_plugin $(prefix)/bin/grpc_python_plugin
@@ -3174,6 +3177,7 @@ endif
 
 LIBGRPC_PLUGIN_SUPPORT_SRC = \
     src/compiler/cpp_generator.cc \
+    src/compiler/csharp_generator.cc \
     src/compiler/objective_c_generator.cc \
     src/compiler/python_generator.cc \
     src/compiler/ruby_generator.cc \
@@ -7099,6 +7103,34 @@ ifneq ($(NO_DEPS),true)
 endif
 
 
+GRPC_CSHARP_PLUGIN_SRC = \
+    src/compiler/csharp_plugin.cc \
+
+GRPC_CSHARP_PLUGIN_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_CSHARP_PLUGIN_SRC))))
+
+ifeq ($(NO_PROTOBUF),true)
+
+# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+.
+
+$(BINDIR)/$(CONFIG)/grpc_csharp_plugin: protobuf_dep_error
+
+else
+
+$(BINDIR)/$(CONFIG)/grpc_csharp_plugin: $(PROTOBUF_DEP) $(GRPC_CSHARP_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
+	$(E) "[HOSTLD]  Linking $@"
+	$(Q) mkdir -p `dirname $@`
+	$(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_CSHARP_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_csharp_plugin
+
+endif
+
+$(OBJDIR)/$(CONFIG)/src/compiler/csharp_plugin.o:  $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a
+deps_grpc_csharp_plugin: $(GRPC_CSHARP_PLUGIN_OBJS:.o=.dep)
+
+ifneq ($(NO_DEPS),true)
+-include $(GRPC_CSHARP_PLUGIN_OBJS:.o=.dep)
+endif
+
+
 GRPC_OBJECTIVE_C_PLUGIN_SRC = \
     src/compiler/objective_c_plugin.cc \
 
diff --git a/build.json b/build.json
index 932e82a6c07692e3c0cedb55c92bec5bf899df7c..cc2dfe8cb9d35e63377238f03277b8d61551d9b4 100644
--- a/build.json
+++ b/build.json
@@ -563,6 +563,8 @@
         "src/compiler/config.h",
         "src/compiler/cpp_generator.h",
         "src/compiler/cpp_generator_helpers.h",
+        "src/compiler/csharp_generator.h",
+        "src/compiler/csharp_generator_helpers.h",
         "src/compiler/generator_helpers.h",
         "src/compiler/objective_c_generator.h",
         "src/compiler/objective_c_generator_helpers.h",
@@ -574,6 +576,7 @@
       ],
       "src": [
         "src/compiler/cpp_generator.cc",
+        "src/compiler/csharp_generator.cc",
         "src/compiler/objective_c_generator.cc",
         "src/compiler/python_generator.cc",
         "src/compiler/ruby_generator.cc"
@@ -1931,6 +1934,18 @@
       ],
       "secure": "no"
     },
+    {
+      "name": "grpc_csharp_plugin",
+      "build": "protoc",
+      "language": "c++",
+      "src": [
+        "src/compiler/csharp_plugin.cc"
+      ],
+      "deps": [
+        "grpc_plugin_support"
+      ],
+      "secure": "no"
+    },
     {
       "name": "grpc_objective_c_plugin",
       "build": "protoc",
diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc
new file mode 100644
index 0000000000000000000000000000000000000000..82dd06bcec4c39cca7d84b081b4dc5d44bdfe0dc
--- /dev/null
+++ b/src/compiler/csharp_generator.cc
@@ -0,0 +1,480 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <cctype>
+#include <map>
+#include <vector>
+
+#include "src/compiler/config.h"
+#include "src/compiler/csharp_generator_helpers.h"
+#include "src/compiler/csharp_generator.h"
+
+using grpc::protobuf::FileDescriptor;
+using grpc::protobuf::Descriptor;
+using grpc::protobuf::ServiceDescriptor;
+using grpc::protobuf::MethodDescriptor;
+using grpc::protobuf::io::Printer;
+using grpc::protobuf::io::StringOutputStream;
+using grpc_generator::MethodType;
+using grpc_generator::GetMethodType;
+using grpc_generator::METHODTYPE_NO_STREAMING;
+using grpc_generator::METHODTYPE_CLIENT_STREAMING;
+using grpc_generator::METHODTYPE_SERVER_STREAMING;
+using grpc_generator::METHODTYPE_BIDI_STREAMING;
+using std::map;
+using std::vector;
+
+namespace grpc_csharp_generator {
+namespace {
+
+std::string GetCSharpNamespace(const FileDescriptor* file) {
+  // TODO(jtattermusch): this should be based on csharp_namespace option
+  return file->package();
+}
+
+std::string GetMessageType(const Descriptor* message) {
+  // TODO(jtattermusch): this has to match with C# protobuf generator
+  return message->name();
+}
+
+std::string GetServiceClassName(const ServiceDescriptor* service) {
+  return service->name();
+}
+
+std::string GetClientInterfaceName(const ServiceDescriptor* service) {
+  return "I" + service->name() + "Client";
+}
+
+std::string GetClientClassName(const ServiceDescriptor* service) {
+  return service->name() + "Client";
+}
+
+std::string GetServerInterfaceName(const ServiceDescriptor* service) {
+  return "I" + service->name();
+}
+
+std::string GetCSharpMethodType(MethodType method_type) {
+  switch (method_type) {
+    case METHODTYPE_NO_STREAMING:
+      return "MethodType.Unary";
+    case METHODTYPE_CLIENT_STREAMING:
+      return "MethodType.ClientStreaming";
+    case METHODTYPE_SERVER_STREAMING:
+      return "MethodType.ServerStreaming";
+    case METHODTYPE_BIDI_STREAMING:
+      return "MethodType.DuplexStreaming";
+  }
+  GOOGLE_LOG(FATAL)<< "Can't get here.";
+  return "";
+}
+
+std::string GetServiceNameFieldName() {
+  return "__ServiceName";
+}
+
+std::string GetMarshallerFieldName(const Descriptor *message) {
+  return "__Marshaller_" + message->name();
+}
+
+std::string GetMethodFieldName(const MethodDescriptor *method) {
+  return "__Method_" + method->name();
+}
+
+std::string GetMethodRequestParamMaybe(const MethodDescriptor *method) {
+  if (method->client_streaming()) {
+    return "";
+  }
+  return GetMessageType(method->input_type()) + " request, ";
+}
+
+std::string GetMethodReturnTypeClient(const MethodDescriptor *method) {
+  switch (GetMethodType(method)) {
+    case METHODTYPE_NO_STREAMING:
+      return "Task<" + GetMessageType(method->output_type()) + ">";
+    case METHODTYPE_CLIENT_STREAMING:
+      return "AsyncClientStreamingCall<" + GetMessageType(method->input_type())
+          + ", " + GetMessageType(method->output_type()) + ">";
+    case METHODTYPE_SERVER_STREAMING:
+      return "AsyncServerStreamingCall<" + GetMessageType(method->output_type())
+          + ">";
+    case METHODTYPE_BIDI_STREAMING:
+      return "AsyncDuplexStreamingCall<" + GetMessageType(method->input_type())
+          + ", " + GetMessageType(method->output_type()) + ">";
+  }
+  GOOGLE_LOG(FATAL)<< "Can't get here.";
+  return "";
+}
+
+std::string GetMethodRequestParamServer(const MethodDescriptor *method) {
+  switch (GetMethodType(method)) {
+    case METHODTYPE_NO_STREAMING:
+    case METHODTYPE_SERVER_STREAMING:
+      return GetMessageType(method->input_type()) + " request";
+    case METHODTYPE_CLIENT_STREAMING:
+    case METHODTYPE_BIDI_STREAMING:
+      return "IAsyncStreamReader<" + GetMessageType(method->input_type())
+          + "> requestStream";
+  }
+  GOOGLE_LOG(FATAL)<< "Can't get here.";
+  return "";
+}
+
+std::string GetMethodReturnTypeServer(const MethodDescriptor *method) {
+  switch (GetMethodType(method)) {
+    case METHODTYPE_NO_STREAMING:
+    case METHODTYPE_CLIENT_STREAMING:
+      return "Task<" + GetMessageType(method->output_type()) + ">";
+    case METHODTYPE_SERVER_STREAMING:
+    case METHODTYPE_BIDI_STREAMING:
+      return "Task";
+  }
+  GOOGLE_LOG(FATAL)<< "Can't get here.";
+  return "";
+}
+
+std::string GetMethodResponseStreamMaybe(const MethodDescriptor *method) {
+  switch (GetMethodType(method)) {
+    case METHODTYPE_NO_STREAMING:
+    case METHODTYPE_CLIENT_STREAMING:
+      return "";
+    case METHODTYPE_SERVER_STREAMING:
+    case METHODTYPE_BIDI_STREAMING:
+      return ", IServerStreamWriter<" + GetMessageType(method->output_type())
+          + "> responseStream";
+  }
+  GOOGLE_LOG(FATAL)<< "Can't get here.";
+  return "";
+}
+
+// Gets vector of all messages used as input or output types.
+std::vector<const Descriptor*> GetUsedMessages(
+    const ServiceDescriptor *service) {
+  std::set<const Descriptor*> descriptor_set;
+  std::vector<const Descriptor*> result;  // vector is to maintain stable ordering
+  for (int i = 0; i < service->method_count(); i++) {
+    const MethodDescriptor *method = service->method(i);
+    if (descriptor_set.find(method->input_type()) == descriptor_set.end()) {
+      descriptor_set.insert(method->input_type());
+      result.push_back(method->input_type());
+    }
+    if (descriptor_set.find(method->output_type()) == descriptor_set.end()) {
+      descriptor_set.insert(method->output_type());
+      result.push_back(method->output_type());
+    }
+  }
+  return result;
+}
+
+void GenerateMarshallerFields(Printer* out, const ServiceDescriptor *service) {
+  std::vector<const Descriptor*> used_messages = GetUsedMessages(service);
+  for (size_t i = 0; i < used_messages.size(); i++) {
+    const Descriptor *message = used_messages[i];
+    out->Print(
+        "static readonly Marshaller<$type$> $fieldname$ = Marshallers.Create((arg) => arg.ToByteArray(), $type$.ParseFrom);\n",
+        "fieldname", GetMarshallerFieldName(message), "type",
+        GetMessageType(message));
+  }
+  out->Print("\n");
+}
+
+void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) {
+  out->Print(
+      "static readonly Method<$request$, $response$> $fieldname$ = new Method<$request$, $response$>(\n",
+      "fieldname", GetMethodFieldName(method), "request",
+      GetMessageType(method->input_type()), "response",
+      GetMessageType(method->output_type()));
+  out->Indent();
+  out->Indent();
+  out->Print("$methodtype$,\n", "methodtype",
+             GetCSharpMethodType(GetMethodType(method)));
+  out->Print("\"$methodname$\",\n", "methodname", method->name());
+  out->Print("$requestmarshaller$,\n", "requestmarshaller",
+             GetMarshallerFieldName(method->input_type()));
+  out->Print("$responsemarshaller$);\n", "responsemarshaller",
+             GetMarshallerFieldName(method->output_type()));
+  out->Print("\n");
+  out->Outdent();
+  out->Outdent();
+}
+
+void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) {
+  out->Print("// client-side stub interface\n");
+  out->Print("public interface $name$\n", "name",
+             GetClientInterfaceName(service));
+  out->Print("{\n");
+  out->Indent();
+  for (int i = 0; i < service->method_count(); i++) {
+    const MethodDescriptor *method = service->method(i);
+    MethodType method_type = GetMethodType(method);
+
+    if (method_type == METHODTYPE_NO_STREAMING) {
+      // unary calls have an extra synchronous stub method
+      out->Print(
+          "$response$ $methodname$($request$ request, CancellationToken token = default(CancellationToken));\n",
+          "methodname", method->name(), "request",
+          GetMessageType(method->input_type()), "response",
+          GetMessageType(method->output_type()));
+    }
+
+    std::string method_name = method->name();
+    if (method_type == METHODTYPE_NO_STREAMING) {
+      method_name += "Async";  // prevent name clash with synchronous method.
+    }
+    out->Print(
+        "$returntype$ $methodname$($request_maybe$CancellationToken token = default(CancellationToken));\n",
+        "methodname", method_name, "request_maybe",
+        GetMethodRequestParamMaybe(method), "returntype",
+        GetMethodReturnTypeClient(method));
+  }
+  out->Outdent();
+  out->Print("}\n");
+  out->Print("\n");
+}
+
+void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) {
+  out->Print("// server-side interface\n");
+  out->Print("public interface $name$\n", "name",
+             GetServerInterfaceName(service));
+  out->Print("{\n");
+  out->Indent();
+  for (int i = 0; i < service->method_count(); i++) {
+    const MethodDescriptor *method = service->method(i);
+    out->Print("$returntype$ $methodname$(ServerCallContext context, $request$$response_stream_maybe$);\n",
+               "methodname", method->name(), "returntype",
+               GetMethodReturnTypeServer(method), "request",
+               GetMethodRequestParamServer(method), "response_stream_maybe",
+               GetMethodResponseStreamMaybe(method));
+  }
+  out->Outdent();
+  out->Print("}\n");
+  out->Print("\n");
+}
+
+void GenerateClientStub(Printer* out, const ServiceDescriptor *service) {
+  out->Print("// client stub\n");
+  out->Print(
+      "public class $name$ : AbstractStub<$name$, StubConfiguration>, $interface$\n",
+      "name", GetClientClassName(service), "interface",
+      GetClientInterfaceName(service));
+  out->Print("{\n");
+  out->Indent();
+
+  // constructors
+  out->Print(
+      "public $name$(Channel channel) : this(channel, StubConfiguration.Default)\n",
+      "name", GetClientClassName(service));
+  out->Print("{\n");
+  out->Print("}\n");
+  out->Print(
+      "public $name$(Channel channel, StubConfiguration config) : base(channel, config)\n",
+      "name", GetClientClassName(service));
+  out->Print("{\n");
+  out->Print("}\n");
+
+  for (int i = 0; i < service->method_count(); i++) {
+    const MethodDescriptor *method = service->method(i);
+    MethodType method_type = GetMethodType(method);
+
+    if (method_type == METHODTYPE_NO_STREAMING) {
+      // unary calls have an extra synchronous stub method
+      out->Print(
+          "public $response$ $methodname$($request$ request, CancellationToken token = default(CancellationToken))\n",
+          "methodname", method->name(), "request",
+          GetMessageType(method->input_type()), "response",
+          GetMessageType(method->output_type()));
+      out->Print("{\n");
+      out->Indent();
+      out->Print("var call = CreateCall($servicenamefield$, $methodfield$);\n",
+                 "servicenamefield", GetServiceNameFieldName(), "methodfield",
+                 GetMethodFieldName(method));
+      out->Print("return Calls.BlockingUnaryCall(call, request, token);\n");
+      out->Outdent();
+      out->Print("}\n");
+    }
+
+    std::string method_name = method->name();
+    if (method_type == METHODTYPE_NO_STREAMING) {
+      method_name += "Async";  // prevent name clash with synchronous method.
+    }
+    out->Print(
+        "public $returntype$ $methodname$($request_maybe$CancellationToken token = default(CancellationToken))\n",
+        "methodname", method_name, "request_maybe",
+        GetMethodRequestParamMaybe(method), "returntype",
+        GetMethodReturnTypeClient(method));
+    out->Print("{\n");
+    out->Indent();
+    out->Print("var call = CreateCall($servicenamefield$, $methodfield$);\n",
+               "servicenamefield", GetServiceNameFieldName(), "methodfield",
+               GetMethodFieldName(method));
+    switch (GetMethodType(method)) {
+      case METHODTYPE_NO_STREAMING:
+        out->Print("return Calls.AsyncUnaryCall(call, request, token);\n");
+        break;
+      case METHODTYPE_CLIENT_STREAMING:
+        out->Print("return Calls.AsyncClientStreamingCall(call, token);\n");
+        break;
+      case METHODTYPE_SERVER_STREAMING:
+        out->Print(
+            "return Calls.AsyncServerStreamingCall(call, request, token);\n");
+        break;
+      case METHODTYPE_BIDI_STREAMING:
+        out->Print("return Calls.AsyncDuplexStreamingCall(call, token);\n");
+        break;
+      default:
+        GOOGLE_LOG(FATAL)<< "Can't get here.";
+      }
+    out->Outdent();
+    out->Print("}\n");
+  }
+  out->Outdent();
+  out->Print("}\n");
+  out->Print("\n");
+}
+
+void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service) {
+  out->Print(
+      "// creates service definition that can be registered with a server\n");
+  out->Print(
+      "public static ServerServiceDefinition BindService($interface$ serviceImpl)\n",
+      "interface", GetServerInterfaceName(service));
+  out->Print("{\n");
+  out->Indent();
+
+  out->Print(
+      "return ServerServiceDefinition.CreateBuilder($servicenamefield$)\n",
+      "servicenamefield", GetServiceNameFieldName());
+  out->Indent();
+  out->Indent();
+  for (int i = 0; i < service->method_count(); i++) {
+    const MethodDescriptor *method = service->method(i);
+    out->Print(".AddMethod($methodfield$, serviceImpl.$methodname$)",
+               "methodfield", GetMethodFieldName(method), "methodname",
+               method->name());
+    if (i == service->method_count() - 1) {
+      out->Print(".Build();");
+    }
+    out->Print("\n");
+  }
+  out->Outdent();
+  out->Outdent();
+
+  out->Outdent();
+  out->Print("}\n");
+  out->Print("\n");
+}
+
+void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) {
+  out->Print("// creates a new client stub\n");
+  out->Print("public static $interface$ NewStub(Channel channel)\n",
+             "interface", GetClientInterfaceName(service));
+  out->Print("{\n");
+  out->Indent();
+  out->Print("return new $classname$(channel);\n", "classname",
+             GetClientClassName(service));
+  out->Outdent();
+  out->Print("}\n");
+  out->Print("\n");
+
+  out->Print("// creates a new client stub\n");
+  out->Print(
+      "public static $interface$ NewStub(Channel channel, StubConfiguration config)\n",
+      "interface", GetClientInterfaceName(service));
+  out->Print("{\n");
+  out->Indent();
+  out->Print("return new $classname$(channel, config);\n", "classname",
+             GetClientClassName(service));
+  out->Outdent();
+  out->Print("}\n");
+}
+
+void GenerateService(Printer* out, const ServiceDescriptor *service) {
+  out->Print("public static class $classname$\n", "classname",
+             GetServiceClassName(service));
+  out->Print("{\n");
+  out->Indent();
+  out->Print("static readonly string $servicenamefield$ = \"$servicename$\";\n",
+             "servicenamefield", GetServiceNameFieldName(), "servicename",
+             service->full_name());
+  out->Print("\n");
+
+  GenerateMarshallerFields(out, service);
+  for (int i = 0; i < service->method_count(); i++) {
+    GenerateStaticMethodField(out, service->method(i));
+  }
+  GenerateClientInterface(out, service);
+  GenerateServerInterface(out, service);
+  GenerateClientStub(out, service);
+  GenerateBindServiceMethod(out, service);
+  GenerateNewStubMethods(out, service);
+
+  out->Outdent();
+  out->Print("}\n");
+}
+
+}  // anonymous namespace
+
+grpc::string GetServices(const FileDescriptor *file) {
+  grpc::string output;
+  StringOutputStream output_stream(&output);
+  Printer out(&output_stream, '$');
+
+  // Don't write out any output if there no services, to avoid empty service
+  // files being generated for proto files that don't declare any.
+  if (file->service_count() == 0) {
+    return output;
+  }
+
+  // Write out a file header.
+  out.Print("// Generated by the protocol buffer compiler.  DO NOT EDIT!\n");
+  out.Print("// source: $filename$\n", "filename", file->name());
+  out.Print("#region Designer generated code\n");
+  out.Print("\n");
+  out.Print("using System;\n");
+  out.Print("using System.Threading;\n");
+  out.Print("using System.Threading.Tasks;\n");
+  out.Print("using Grpc.Core;\n");
+  // TODO(jtattermusch): add using for protobuf message classes
+  out.Print("\n");
+
+  out.Print("namespace $namespace$ {\n", "namespace", GetCSharpNamespace(file));
+  out.Indent();
+  for (int i = 0; i < file->service_count(); i++) {
+    GenerateService(&out, file->service(i));
+  }
+  out.Outdent();
+  out.Print("}\n");
+  out.Print("#endregion\n");
+  return output;
+}
+
+}  // namespace grpc_csharp_generator
diff --git a/src/compiler/csharp_generator.h b/src/compiler/csharp_generator.h
new file mode 100644
index 0000000000000000000000000000000000000000..ec537d3f1d2835c6bea91df798224129d72cf05e
--- /dev/null
+++ b/src/compiler/csharp_generator.h
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_H
+#define GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_H
+
+#include "src/compiler/config.h"
+
+namespace grpc_csharp_generator {
+
+grpc::string GetServices(const grpc::protobuf::FileDescriptor *file);
+
+}  // namespace grpc_csharp_generator
+
+#endif  // GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_H
diff --git a/src/compiler/csharp_generator_helpers.h b/src/compiler/csharp_generator_helpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..137062763374fcf59e44e62a355e2cbb2cee967d
--- /dev/null
+++ b/src/compiler/csharp_generator_helpers.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_HELPERS_H
+#define GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_HELPERS_H
+
+#include "src/compiler/config.h"
+#include "src/compiler/generator_helpers.h"
+
+namespace grpc_csharp_generator {
+
+inline bool ServicesFilename(const grpc::protobuf::FileDescriptor *file,
+                             grpc::string *file_name_or_error) {
+  *file_name_or_error = grpc_generator::FileNameInUpperCamel(file) + "Grpc.cs";
+  return true;
+}
+
+}  // namespace grpc_csharp_generator
+
+#endif  // GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_HELPERS_H
diff --git a/src/compiler/csharp_plugin.cc b/src/compiler/csharp_plugin.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8b9395f9e2b21af8c5af6e255e27724d14bbbff0
--- /dev/null
+++ b/src/compiler/csharp_plugin.cc
@@ -0,0 +1,72 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+// Generates C# gRPC service interface out of Protobuf IDL.
+
+#include <memory>
+
+#include "src/compiler/config.h"
+#include "src/compiler/csharp_generator.h"
+#include "src/compiler/csharp_generator_helpers.h"
+
+class CSharpGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
+ public:
+  CSharpGrpcGenerator() {}
+  ~CSharpGrpcGenerator() {}
+
+  bool Generate(const grpc::protobuf::FileDescriptor *file,
+                const grpc::string &parameter,
+                grpc::protobuf::compiler::GeneratorContext *context,
+                grpc::string *error) const {
+    grpc::string code = grpc_csharp_generator::GetServices(file);
+    if (code.size() == 0) {
+      return true;  // don't generate a file if there are no services
+    }
+
+    // Get output file name.
+    grpc::string file_name;
+    if (!grpc_csharp_generator::ServicesFilename(file, &file_name)) {
+      return false;
+    }
+    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> output(
+        context->Open(file_name));
+    grpc::protobuf::io::CodedOutputStream coded_out(output.get());
+    coded_out.WriteRaw(code.data(), code.size());
+    return true;
+  }
+};
+
+int main(int argc, char *argv[]) {
+  CSharpGrpcGenerator generator;
+  return grpc::protobuf::compiler::PluginMain(argc, argv, &generator);
+}
diff --git a/src/compiler/generator_helpers.h b/src/compiler/generator_helpers.h
index 374e1374cf4c4a12de9fdabe1f28759d4bf2a671..7ce4ec526c98865a76a84afd4346ce2c6b0c616b 100644
--- a/src/compiler/generator_helpers.h
+++ b/src/compiler/generator_helpers.h
@@ -116,6 +116,29 @@ inline grpc::string FileNameInUpperCamel(const grpc::protobuf::FileDescriptor *f
   return LowerUnderscoreToUpperCamel(StripProto(file->name()));
 }
 
+enum MethodType {
+  METHODTYPE_NO_STREAMING,
+  METHODTYPE_CLIENT_STREAMING,
+  METHODTYPE_SERVER_STREAMING,
+  METHODTYPE_BIDI_STREAMING
+};
+
+inline MethodType GetMethodType(const grpc::protobuf::MethodDescriptor *method) {
+  if (method->client_streaming()) {
+    if (method->server_streaming()) {
+      return METHODTYPE_BIDI_STREAMING;
+    } else {
+      return METHODTYPE_CLIENT_STREAMING;
+    }
+  } else {
+    if (method->server_streaming()) {
+      return METHODTYPE_SERVER_STREAMING;
+    } else {
+      return METHODTYPE_NO_STREAMING;
+    }
+  }
+}
+
 }  // namespace grpc_generator
 
 #endif  // GRPC_INTERNAL_COMPILER_GENERATOR_HELPERS_H
diff --git a/src/csharp/Grpc.Examples.MathClient/MathClient.cs b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
index ca7683d399ff2cfbfd278c3cd171e1d41d6eaa34..85d9cdc7a61468e063042332cce07baa0ad9f501 100644
--- a/src/csharp/Grpc.Examples.MathClient/MathClient.cs
+++ b/src/csharp/Grpc.Examples.MathClient/MathClient.cs
@@ -43,7 +43,7 @@ namespace math
 
             using (Channel channel = new Channel("127.0.0.1:23456"))
             {
-                MathGrpc.IMathServiceClient stub = new MathGrpc.MathServiceClientStub(channel);
+                Math.IMathClient stub = new Math.MathClient(channel);
                 MathExamples.DivExample(stub);
 
                 MathExamples.DivAsyncExample(stub).Wait();
diff --git a/src/csharp/Grpc.Examples.MathServer/MathServer.cs b/src/csharp/Grpc.Examples.MathServer/MathServer.cs
index cfde9b42c76eb736550686978c3381d66e54d46d..d05e3f28080cc167dcede1f551683c212c13e221 100644
--- a/src/csharp/Grpc.Examples.MathServer/MathServer.cs
+++ b/src/csharp/Grpc.Examples.MathServer/MathServer.cs
@@ -45,7 +45,7 @@ namespace math
             GrpcEnvironment.Initialize();
 
             Server server = new Server();
-            server.AddServiceDefinition(MathGrpc.BindService(new MathServiceImpl()));
+            server.AddServiceDefinition(Math.BindService(new MathServiceImpl()));
             int port = server.AddListeningPort(host, 23456);
             server.Start();
 
diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
index 4ada95edd6da174fae3a1abe6ae4c5429b77d266..2d20b0403a48ea1c3696d2c58356b1c24672772d 100644
--- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
+++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs
@@ -50,7 +50,7 @@ namespace math.Tests
         string host = "localhost";
         Server server;
         Channel channel;
-        MathGrpc.IMathServiceClient client;
+        Math.IMathClient client;
 
         [TestFixtureSetUp]
         public void Init()
@@ -58,7 +58,7 @@ namespace math.Tests
             GrpcEnvironment.Initialize();
 
             server = new Server();
-            server.AddServiceDefinition(MathGrpc.BindService(new MathServiceImpl()));
+            server.AddServiceDefinition(Math.BindService(new MathServiceImpl()));
             int port = server.AddListeningPort(host, Server.PickUnusedPort);
             server.Start();
             channel = new Channel(host + ":" + port);
@@ -69,7 +69,7 @@ namespace math.Tests
             {
                 headerBuilder.Add(new Metadata.MetadataEntry("customHeader", "abcdef"));
             });
-            client = MathGrpc.NewStub(channel, stubConfig);
+            client = Math.NewStub(channel, stubConfig);
         }
 
         [TestFixtureTearDown]
diff --git a/src/csharp/Grpc.Examples/MathExamples.cs b/src/csharp/Grpc.Examples/MathExamples.cs
index dba5a7736cd4f8855c8e3717a80e6e3bcce8d894..d8ea8566cb933a835f78bac55870bc371ad4e21f 100644
--- a/src/csharp/Grpc.Examples/MathExamples.cs
+++ b/src/csharp/Grpc.Examples/MathExamples.cs
@@ -39,34 +39,34 @@ namespace math
 {
     public static class MathExamples
     {
-        public static void DivExample(MathGrpc.IMathServiceClient stub)
+        public static void DivExample(Math.IMathClient stub)
         {
             DivReply result = stub.Div(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build());
             Console.WriteLine("Div Result: " + result);
         }
 
-        public static async Task DivAsyncExample(MathGrpc.IMathServiceClient stub)
+        public static async Task DivAsyncExample(Math.IMathClient stub)
         {
             Task<DivReply> resultTask = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
             DivReply result = await resultTask;
             Console.WriteLine("DivAsync Result: " + result);
         }
 
-        public static async Task DivAsyncWithCancellationExample(MathGrpc.IMathServiceClient stub)
+        public static async Task DivAsyncWithCancellationExample(Math.IMathClient stub)
         {
             Task<DivReply> resultTask = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build());
             DivReply result = await resultTask;
             Console.WriteLine(result);
         }
 
-        public static async Task FibExample(MathGrpc.IMathServiceClient stub)
+        public static async Task FibExample(Math.IMathClient stub)
         {
             var call = stub.Fib(new FibArgs.Builder { Limit = 5 }.Build());
             List<Num> result = await call.ResponseStream.ToList();
             Console.WriteLine("Fib Result: " + string.Join("|", result));
         }
 
-        public static async Task SumExample(MathGrpc.IMathServiceClient stub)
+        public static async Task SumExample(Math.IMathClient stub)
         {
             var numbers = new List<Num>
             {
@@ -80,7 +80,7 @@ namespace math
             Console.WriteLine("Sum Result: " + await call.Result);
         }
 
-        public static async Task DivManyExample(MathGrpc.IMathServiceClient stub)
+        public static async Task DivManyExample(Math.IMathClient stub)
         {
             var divArgsList = new List<DivArgs>
             {
@@ -93,7 +93,7 @@ namespace math
             Console.WriteLine("DivMany Result: " + string.Join("|", await call.ResponseStream.ToList()));
         }
 
-        public static async Task DependendRequestsExample(MathGrpc.IMathServiceClient stub)
+        public static async Task DependendRequestsExample(Math.IMathClient stub)
         {
             var numbers = new List<Num>
             {
diff --git a/src/csharp/Grpc.Examples/MathGrpc.cs b/src/csharp/Grpc.Examples/MathGrpc.cs
index 03f5c31cb7acc77e7c846f93e08daa3c9cad1847..2546fd220de0eea34ff422041c1971ce2a0e5ae0 100644
--- a/src/csharp/Grpc.Examples/MathGrpc.cs
+++ b/src/csharp/Grpc.Examples/MathGrpc.cs
@@ -1,164 +1,122 @@
-#region Copyright notice and license
-
-// Copyright 2015, Google Inc.
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#endregion
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: math.proto
+#region Designer generated code
 
 using System;
-using System.Collections.Generic;
-using System.Reactive.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using Grpc.Core;
 
-namespace math
-{
-    /// <summary>
-    /// Math service definitions (this is handwritten version of code that will normally be generated).
-    /// </summary>
-    public class MathGrpc
+namespace math {
+  public static class Math
+  {
+    static readonly string __ServiceName = "math.Math";
+
+    static readonly Marshaller<DivArgs> __Marshaller_DivArgs = Marshallers.Create((arg) => arg.ToByteArray(), DivArgs.ParseFrom);
+    static readonly Marshaller<DivReply> __Marshaller_DivReply = Marshallers.Create((arg) => arg.ToByteArray(), DivReply.ParseFrom);
+    static readonly Marshaller<FibArgs> __Marshaller_FibArgs = Marshallers.Create((arg) => arg.ToByteArray(), FibArgs.ParseFrom);
+    static readonly Marshaller<Num> __Marshaller_Num = Marshallers.Create((arg) => arg.ToByteArray(), Num.ParseFrom);
+
+    static readonly Method<DivArgs, DivReply> __Method_Div = new Method<DivArgs, DivReply>(
+        MethodType.Unary,
+        "Div",
+        __Marshaller_DivArgs,
+        __Marshaller_DivReply);
+
+    static readonly Method<DivArgs, DivReply> __Method_DivMany = new Method<DivArgs, DivReply>(
+        MethodType.DuplexStreaming,
+        "DivMany",
+        __Marshaller_DivArgs,
+        __Marshaller_DivReply);
+
+    static readonly Method<FibArgs, Num> __Method_Fib = new Method<FibArgs, Num>(
+        MethodType.ServerStreaming,
+        "Fib",
+        __Marshaller_FibArgs,
+        __Marshaller_Num);
+
+    static readonly Method<Num, Num> __Method_Sum = new Method<Num, Num>(
+        MethodType.ClientStreaming,
+        "Sum",
+        __Marshaller_Num,
+        __Marshaller_Num);
+
+    // client-side stub interface
+    public interface IMathClient
     {
-        static readonly string ServiceName = "/math.Math";
-
-        static readonly Marshaller<DivArgs> DivArgsMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), DivArgs.ParseFrom);
-        static readonly Marshaller<DivReply> DivReplyMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), DivReply.ParseFrom);
-        static readonly Marshaller<Num> NumMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), Num.ParseFrom);
-        static readonly Marshaller<FibArgs> FibArgsMarshaller = Marshallers.Create((arg) => arg.ToByteArray(), FibArgs.ParseFrom);
-
-        static readonly Method<DivArgs, DivReply> DivMethod = new Method<DivArgs, DivReply>(
-            MethodType.Unary,
-            "Div",
-            DivArgsMarshaller,
-            DivReplyMarshaller);
-
-        static readonly Method<FibArgs, Num> FibMethod = new Method<FibArgs, Num>(
-            MethodType.ServerStreaming,
-            "Fib",
-            FibArgsMarshaller,
-            NumMarshaller);
-
-        static readonly Method<Num, Num> SumMethod = new Method<Num, Num>(
-            MethodType.ClientStreaming,
-            "Sum",
-            NumMarshaller,
-            NumMarshaller);
-
-        static readonly Method<DivArgs, DivReply> DivManyMethod = new Method<DivArgs, DivReply>(
-            MethodType.DuplexStreaming,
-            "DivMany",
-            DivArgsMarshaller,
-            DivReplyMarshaller);
-
-        public interface IMathServiceClient
-        {
-            DivReply Div(DivArgs request, CancellationToken token = default(CancellationToken));
-
-            Task<DivReply> DivAsync(DivArgs request, CancellationToken token = default(CancellationToken));
-
-            AsyncServerStreamingCall<Num> Fib(FibArgs request, CancellationToken token = default(CancellationToken));
-
-            AsyncClientStreamingCall<Num, Num> Sum(CancellationToken token = default(CancellationToken));
-
-            AsyncDuplexStreamingCall<DivArgs, DivReply> DivMany(CancellationToken token = default(CancellationToken));
-        }
-
-        public class MathServiceClientStub : AbstractStub<MathServiceClientStub, StubConfiguration>, IMathServiceClient
-        {
-            public MathServiceClientStub(Channel channel) : this(channel, StubConfiguration.Default)
-            {
-            }
-
-            public MathServiceClientStub(Channel channel, StubConfiguration config) : base(channel, config)
-            {
-            }
-
-            public DivReply Div(DivArgs request, CancellationToken token = default(CancellationToken))
-            {
-                var call = CreateCall(ServiceName, DivMethod);
-                return Calls.BlockingUnaryCall(call, request, token);
-            }
-
-            public Task<DivReply> DivAsync(DivArgs request, CancellationToken token = default(CancellationToken))
-            {
-                var call = CreateCall(ServiceName, DivMethod);
-                return Calls.AsyncUnaryCall(call, request, token);
-            }
-
-            public AsyncServerStreamingCall<Num> Fib(FibArgs request, CancellationToken token = default(CancellationToken))
-            {
-                var call = CreateCall(ServiceName, FibMethod);
-                return Calls.AsyncServerStreamingCall(call, request, token);
-            }
-
-            public AsyncClientStreamingCall<Num, Num> Sum(CancellationToken token = default(CancellationToken))
-            {
-                var call = CreateCall(ServiceName, SumMethod);
-                return Calls.AsyncClientStreamingCall(call, token);
-            }
-
-            public AsyncDuplexStreamingCall<DivArgs, DivReply> DivMany(CancellationToken token = default(CancellationToken))
-            {
-                var call = CreateCall(ServiceName, DivManyMethod);
-                return Calls.AsyncDuplexStreamingCall(call, token);
-            }
-        }
-
-        // server-side interface
-        public interface IMathService
-        {
-            Task<DivReply> Div(ServerCallContext context, DivArgs request);
-
-            Task Fib(ServerCallContext context, FibArgs request, IServerStreamWriter<Num> responseStream);
+      DivReply Div(DivArgs request, CancellationToken token = default(CancellationToken));
+      Task<DivReply> DivAsync(DivArgs request, CancellationToken token = default(CancellationToken));
+      AsyncDuplexStreamingCall<DivArgs, DivReply> DivMany(CancellationToken token = default(CancellationToken));
+      AsyncServerStreamingCall<Num> Fib(FibArgs request, CancellationToken token = default(CancellationToken));
+      AsyncClientStreamingCall<Num, Num> Sum(CancellationToken token = default(CancellationToken));
+    }
 
-            Task<Num> Sum(ServerCallContext context, IAsyncStreamReader<Num> requestStream);
+    // server-side interface
+    public interface IMath
+    {
+      Task<DivReply> Div(ServerCallContext context, DivArgs request);
+      Task DivMany(ServerCallContext context, IAsyncStreamReader<DivArgs> requestStream, IServerStreamWriter<DivReply> responseStream);
+      Task Fib(ServerCallContext context, FibArgs request, IServerStreamWriter<Num> responseStream);
+      Task<Num> Sum(ServerCallContext context, IAsyncStreamReader<Num> requestStream);
+    }
 
-            Task DivMany(ServerCallContext context, IAsyncStreamReader<DivArgs> requestStream, IServerStreamWriter<DivReply> responseStream);
-        }
+    // client stub
+    public class MathClient : AbstractStub<MathClient, StubConfiguration>, IMathClient
+    {
+      public MathClient(Channel channel) : this(channel, StubConfiguration.Default)
+      {
+      }
+      public MathClient(Channel channel, StubConfiguration config) : base(channel, config)
+      {
+      }
+      public DivReply Div(DivArgs request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_Div);
+        return Calls.BlockingUnaryCall(call, request, token);
+      }
+      public Task<DivReply> DivAsync(DivArgs request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_Div);
+        return Calls.AsyncUnaryCall(call, request, token);
+      }
+      public AsyncDuplexStreamingCall<DivArgs, DivReply> DivMany(CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_DivMany);
+        return Calls.AsyncDuplexStreamingCall(call, token);
+      }
+      public AsyncServerStreamingCall<Num> Fib(FibArgs request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_Fib);
+        return Calls.AsyncServerStreamingCall(call, request, token);
+      }
+      public AsyncClientStreamingCall<Num, Num> Sum(CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_Sum);
+        return Calls.AsyncClientStreamingCall(call, token);
+      }
+    }
 
-        public static ServerServiceDefinition BindService(IMathService serviceImpl)
-        {
-            return ServerServiceDefinition.CreateBuilder(ServiceName)
-                .AddMethod(DivMethod, serviceImpl.Div)
-                .AddMethod(FibMethod, serviceImpl.Fib)
-                .AddMethod(SumMethod, serviceImpl.Sum)
-                .AddMethod(DivManyMethod, serviceImpl.DivMany).Build();
-        }
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(IMath serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_Div, serviceImpl.Div)
+          .AddMethod(__Method_DivMany, serviceImpl.DivMany)
+          .AddMethod(__Method_Fib, serviceImpl.Fib)
+          .AddMethod(__Method_Sum, serviceImpl.Sum).Build();
+    }
 
-        public static IMathServiceClient NewStub(Channel channel)
-        {
-            return new MathServiceClientStub(channel);
-        }
+    // creates a new client stub
+    public static IMathClient NewStub(Channel channel)
+    {
+      return new MathClient(channel);
+    }
 
-        public static IMathServiceClient NewStub(Channel channel, StubConfiguration config)
-        {
-            return new MathServiceClientStub(channel, config);
-        }
+    // creates a new client stub
+    public static IMathClient NewStub(Channel channel, StubConfiguration config)
+    {
+      return new MathClient(channel, config);
     }
+  }
 }
+#endregion
diff --git a/src/csharp/Grpc.Examples/MathServiceImpl.cs b/src/csharp/Grpc.Examples/MathServiceImpl.cs
index 800dee8735412a037d76c831a9e953c042feff62..16d7724178f5c34e935d0a923812571be823703c 100644
--- a/src/csharp/Grpc.Examples/MathServiceImpl.cs
+++ b/src/csharp/Grpc.Examples/MathServiceImpl.cs
@@ -44,7 +44,7 @@ namespace math
     /// <summary>
     /// Implementation of MathService server
     /// </summary>
-    public class MathServiceImpl : MathGrpc.IMathService
+    public class MathServiceImpl : Math.IMath
     {
         public Task<DivReply> Div(ServerCallContext context, DivArgs request)
         {
diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
index 13bbb5363fa41ad8e9837e7ce9863af628d92d7c..1ca3dd24e1cdfdc86e21867363093621b9660fe8 100644
--- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
+++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj
@@ -3,7 +3,7 @@
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
-    <ProductVersion>10.0.0</ProductVersion>
+    <ProductVersion>8.0.30703</ProductVersion>
     <SchemaVersion>2.0</SchemaVersion>
     <ProjectGuid>{C61154BA-DD4A-4838-8420-0162A28925E0}</ProjectGuid>
     <OutputType>Library</OutputType>
@@ -72,7 +72,6 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="TestServiceGrpc.cs" />
     <Compile Include="Empty.cs" />
     <Compile Include="Messages.cs" />
     <Compile Include="InteropClientServerTest.cs" />
@@ -80,6 +79,7 @@
     <Compile Include="InteropServer.cs" />
     <Compile Include="InteropClient.cs" />
     <Compile Include="TestCredentials.cs" />
+    <Compile Include="TestGrpc.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
index a433659a086de3e7d353bcb0f919aa8a9e4381a0..02f8a369defa37d64f0b39288fe5ee1749f515bb 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs
@@ -132,14 +132,14 @@ namespace Grpc.IntegrationTesting
                     stubConfig = new StubConfiguration(OAuth2InterceptorFactory.Create(credential));
                 }
 
-                TestServiceGrpc.ITestServiceClient client = new TestServiceGrpc.TestServiceClientStub(channel, stubConfig);
+                TestService.ITestServiceClient client = new TestService.TestServiceClient(channel, stubConfig);
                 RunTestCase(options.testCase, client);
             }
 
             GrpcEnvironment.Shutdown();
         }
 
-        private void RunTestCase(string testCase, TestServiceGrpc.ITestServiceClient client)
+        private void RunTestCase(string testCase, TestService.ITestServiceClient client)
         {
             switch (testCase)
             {
@@ -181,7 +181,7 @@ namespace Grpc.IntegrationTesting
             }
         }
 
-        public static void RunEmptyUnary(TestServiceGrpc.ITestServiceClient client)
+        public static void RunEmptyUnary(TestService.ITestServiceClient client)
         {
             Console.WriteLine("running empty_unary");
             var response = client.EmptyCall(Empty.DefaultInstance);
@@ -189,7 +189,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static void RunLargeUnary(TestServiceGrpc.ITestServiceClient client)
+        public static void RunLargeUnary(TestService.ITestServiceClient client)
         {
             Console.WriteLine("running large_unary");
             var request = SimpleRequest.CreateBuilder()
@@ -205,7 +205,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static void RunClientStreaming(TestServiceGrpc.ITestServiceClient client)
+        public static void RunClientStreaming(TestService.ITestServiceClient client)
         {
             Task.Run(async () =>
             {
@@ -222,7 +222,7 @@ namespace Grpc.IntegrationTesting
             }).Wait();
         }
 
-        public static void RunServerStreaming(TestServiceGrpc.ITestServiceClient client)
+        public static void RunServerStreaming(TestService.ITestServiceClient client)
         {
             Task.Run(async () =>
             {
@@ -248,7 +248,7 @@ namespace Grpc.IntegrationTesting
             }).Wait();
         }
 
-        public static void RunPingPong(TestServiceGrpc.ITestServiceClient client)
+        public static void RunPingPong(TestService.ITestServiceClient client)
         {
             Task.Run(async () =>
             {
@@ -303,7 +303,7 @@ namespace Grpc.IntegrationTesting
             }).Wait();
         }
 
-        public static void RunEmptyStream(TestServiceGrpc.ITestServiceClient client)
+        public static void RunEmptyStream(TestService.ITestServiceClient client)
         {
             Task.Run(async () =>
             {
@@ -318,7 +318,7 @@ namespace Grpc.IntegrationTesting
             }).Wait();
         }
 
-        public static void RunServiceAccountCreds(TestServiceGrpc.ITestServiceClient client)
+        public static void RunServiceAccountCreds(TestService.ITestServiceClient client)
         {
             Console.WriteLine("running service_account_creds");
             var request = SimpleRequest.CreateBuilder()
@@ -338,7 +338,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static void RunComputeEngineCreds(TestServiceGrpc.ITestServiceClient client)
+        public static void RunComputeEngineCreds(TestService.ITestServiceClient client)
         {
             Console.WriteLine("running compute_engine_creds");
             var request = SimpleRequest.CreateBuilder()
@@ -358,7 +358,7 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
         }
 
-        public static void RunCancelAfterBegin(TestServiceGrpc.ITestServiceClient client)
+        public static void RunCancelAfterBegin(TestService.ITestServiceClient client)
         {
             Task.Run(async () =>
             {
@@ -383,7 +383,7 @@ namespace Grpc.IntegrationTesting
             }).Wait();
         }
 
-        public static void RunCancelAfterFirstResponse(TestServiceGrpc.ITestServiceClient client)
+        public static void RunCancelAfterFirstResponse(TestService.ITestServiceClient client)
         {
             Task.Run(async () =>
             {
@@ -419,7 +419,7 @@ namespace Grpc.IntegrationTesting
         }
 
         // This is not an official interop test, but it's useful.
-        public static void RunBenchmarkEmptyUnary(TestServiceGrpc.ITestServiceClient client)
+        public static void RunBenchmarkEmptyUnary(TestService.ITestServiceClient client)
         {
             BenchmarkUtil.RunBenchmark(10000, 10000,
                                        () => { client.EmptyCall(Empty.DefaultInstance); });
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
index 9e49ce0d174d800bb829cac7e6338cc1321771cf..ddbfc61a4ef74acc08500d77c384da579d090621 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs
@@ -50,7 +50,7 @@ namespace Grpc.IntegrationTesting
         string host = "localhost";
         Server server;
         Channel channel;
-        TestServiceGrpc.ITestServiceClient client;
+        TestService.ITestServiceClient client;
 
         [TestFixtureSetUp]
         public void Init()
@@ -58,7 +58,7 @@ namespace Grpc.IntegrationTesting
             GrpcEnvironment.Initialize();
 
             server = new Server();
-            server.AddServiceDefinition(TestServiceGrpc.BindService(new TestServiceImpl()));
+            server.AddServiceDefinition(TestService.BindService(new TestServiceImpl()));
             int port = server.AddListeningPort(host, Server.PickUnusedPort, TestCredentials.CreateTestServerCredentials());
             server.Start();
 
@@ -66,7 +66,7 @@ namespace Grpc.IntegrationTesting
                 .AddString(ChannelArgs.SslTargetNameOverrideKey, TestCredentials.DefaultHostOverride).Build();
 
             channel = new Channel(host + ":" + port, TestCredentials.CreateTestClientCredentials(true), channelArgs);
-            client = TestServiceGrpc.NewStub(channel);
+            client = TestService.NewStub(channel);
         }
 
         [TestFixtureTearDown]
diff --git a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
index ca54aed04188c4b69ccf57a5c8bd5786ed483f11..87c3cbe1d4cf3565dd5ba5e8d95d22e2aa186350 100644
--- a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
+++ b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs
@@ -91,7 +91,7 @@ namespace Grpc.IntegrationTesting
             GrpcEnvironment.Initialize();
 
             var server = new Server();
-            server.AddServiceDefinition(TestServiceGrpc.BindService(new TestServiceImpl()));
+            server.AddServiceDefinition(TestService.BindService(new TestServiceImpl()));
 
             string host = "0.0.0.0";
             int port = options.port.Value;
diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
new file mode 100644
index 0000000000000000000000000000000000000000..679aafb57a9804636519b3f11912bae6ae474f2f
--- /dev/null
+++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs
@@ -0,0 +1,159 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: test.proto
+#region Designer generated code
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+
+namespace grpc.testing {
+  public static class TestService
+  {
+    static readonly string __ServiceName = "grpc.testing.TestService";
+
+    static readonly Marshaller<Empty> __Marshaller_Empty = Marshallers.Create((arg) => arg.ToByteArray(), Empty.ParseFrom);
+    static readonly Marshaller<SimpleRequest> __Marshaller_SimpleRequest = Marshallers.Create((arg) => arg.ToByteArray(), SimpleRequest.ParseFrom);
+    static readonly Marshaller<SimpleResponse> __Marshaller_SimpleResponse = Marshallers.Create((arg) => arg.ToByteArray(), SimpleResponse.ParseFrom);
+    static readonly Marshaller<StreamingOutputCallRequest> __Marshaller_StreamingOutputCallRequest = Marshallers.Create((arg) => arg.ToByteArray(), StreamingOutputCallRequest.ParseFrom);
+    static readonly Marshaller<StreamingOutputCallResponse> __Marshaller_StreamingOutputCallResponse = Marshallers.Create((arg) => arg.ToByteArray(), StreamingOutputCallResponse.ParseFrom);
+    static readonly Marshaller<StreamingInputCallRequest> __Marshaller_StreamingInputCallRequest = Marshallers.Create((arg) => arg.ToByteArray(), StreamingInputCallRequest.ParseFrom);
+    static readonly Marshaller<StreamingInputCallResponse> __Marshaller_StreamingInputCallResponse = Marshallers.Create((arg) => arg.ToByteArray(), StreamingInputCallResponse.ParseFrom);
+
+    static readonly Method<Empty, Empty> __Method_EmptyCall = new Method<Empty, Empty>(
+        MethodType.Unary,
+        "EmptyCall",
+        __Marshaller_Empty,
+        __Marshaller_Empty);
+
+    static readonly Method<SimpleRequest, SimpleResponse> __Method_UnaryCall = new Method<SimpleRequest, SimpleResponse>(
+        MethodType.Unary,
+        "UnaryCall",
+        __Marshaller_SimpleRequest,
+        __Marshaller_SimpleResponse);
+
+    static readonly Method<StreamingOutputCallRequest, StreamingOutputCallResponse> __Method_StreamingOutputCall = new Method<StreamingOutputCallRequest, StreamingOutputCallResponse>(
+        MethodType.ServerStreaming,
+        "StreamingOutputCall",
+        __Marshaller_StreamingOutputCallRequest,
+        __Marshaller_StreamingOutputCallResponse);
+
+    static readonly Method<StreamingInputCallRequest, StreamingInputCallResponse> __Method_StreamingInputCall = new Method<StreamingInputCallRequest, StreamingInputCallResponse>(
+        MethodType.ClientStreaming,
+        "StreamingInputCall",
+        __Marshaller_StreamingInputCallRequest,
+        __Marshaller_StreamingInputCallResponse);
+
+    static readonly Method<StreamingOutputCallRequest, StreamingOutputCallResponse> __Method_FullDuplexCall = new Method<StreamingOutputCallRequest, StreamingOutputCallResponse>(
+        MethodType.DuplexStreaming,
+        "FullDuplexCall",
+        __Marshaller_StreamingOutputCallRequest,
+        __Marshaller_StreamingOutputCallResponse);
+
+    static readonly Method<StreamingOutputCallRequest, StreamingOutputCallResponse> __Method_HalfDuplexCall = new Method<StreamingOutputCallRequest, StreamingOutputCallResponse>(
+        MethodType.DuplexStreaming,
+        "HalfDuplexCall",
+        __Marshaller_StreamingOutputCallRequest,
+        __Marshaller_StreamingOutputCallResponse);
+
+    // client-side stub interface
+    public interface ITestServiceClient
+    {
+      Empty EmptyCall(Empty request, CancellationToken token = default(CancellationToken));
+      Task<Empty> EmptyCallAsync(Empty request, CancellationToken token = default(CancellationToken));
+      SimpleResponse UnaryCall(SimpleRequest request, CancellationToken token = default(CancellationToken));
+      Task<SimpleResponse> UnaryCallAsync(SimpleRequest request, CancellationToken token = default(CancellationToken));
+      AsyncServerStreamingCall<StreamingOutputCallResponse> StreamingOutputCall(StreamingOutputCallRequest request, CancellationToken token = default(CancellationToken));
+      AsyncClientStreamingCall<StreamingInputCallRequest, StreamingInputCallResponse> StreamingInputCall(CancellationToken token = default(CancellationToken));
+      AsyncDuplexStreamingCall<StreamingOutputCallRequest, StreamingOutputCallResponse> FullDuplexCall(CancellationToken token = default(CancellationToken));
+      AsyncDuplexStreamingCall<StreamingOutputCallRequest, StreamingOutputCallResponse> HalfDuplexCall(CancellationToken token = default(CancellationToken));
+    }
+
+    // server-side interface
+    public interface ITestService
+    {
+      Task<Empty> EmptyCall(ServerCallContext context, Empty request);
+      Task<SimpleResponse> UnaryCall(ServerCallContext context, SimpleRequest request);
+      Task StreamingOutputCall(ServerCallContext context, StreamingOutputCallRequest request, IServerStreamWriter<StreamingOutputCallResponse> responseStream);
+      Task<StreamingInputCallResponse> StreamingInputCall(ServerCallContext context, IAsyncStreamReader<StreamingInputCallRequest> requestStream);
+      Task FullDuplexCall(ServerCallContext context, IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream);
+      Task HalfDuplexCall(ServerCallContext context, IAsyncStreamReader<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> responseStream);
+    }
+
+    // client stub
+    public class TestServiceClient : AbstractStub<TestServiceClient, StubConfiguration>, ITestServiceClient
+    {
+      public TestServiceClient(Channel channel) : this(channel, StubConfiguration.Default)
+      {
+      }
+      public TestServiceClient(Channel channel, StubConfiguration config) : base(channel, config)
+      {
+      }
+      public Empty EmptyCall(Empty request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_EmptyCall);
+        return Calls.BlockingUnaryCall(call, request, token);
+      }
+      public Task<Empty> EmptyCallAsync(Empty request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_EmptyCall);
+        return Calls.AsyncUnaryCall(call, request, token);
+      }
+      public SimpleResponse UnaryCall(SimpleRequest request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_UnaryCall);
+        return Calls.BlockingUnaryCall(call, request, token);
+      }
+      public Task<SimpleResponse> UnaryCallAsync(SimpleRequest request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_UnaryCall);
+        return Calls.AsyncUnaryCall(call, request, token);
+      }
+      public AsyncServerStreamingCall<StreamingOutputCallResponse> StreamingOutputCall(StreamingOutputCallRequest request, CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_StreamingOutputCall);
+        return Calls.AsyncServerStreamingCall(call, request, token);
+      }
+      public AsyncClientStreamingCall<StreamingInputCallRequest, StreamingInputCallResponse> StreamingInputCall(CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_StreamingInputCall);
+        return Calls.AsyncClientStreamingCall(call, token);
+      }
+      public AsyncDuplexStreamingCall<StreamingOutputCallRequest, StreamingOutputCallResponse> FullDuplexCall(CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_FullDuplexCall);
+        return Calls.AsyncDuplexStreamingCall(call, token);
+      }
+      public AsyncDuplexStreamingCall<StreamingOutputCallRequest, StreamingOutputCallResponse> HalfDuplexCall(CancellationToken token = default(CancellationToken))
+      {
+        var call = CreateCall(__ServiceName, __Method_HalfDuplexCall);
+        return Calls.AsyncDuplexStreamingCall(call, token);
+      }
+    }
+
+    // creates service definition that can be registered with a server
+    public static ServerServiceDefinition BindService(ITestService serviceImpl)
+    {
+      return ServerServiceDefinition.CreateBuilder(__ServiceName)
+          .AddMethod(__Method_EmptyCall, serviceImpl.EmptyCall)
+          .AddMethod(__Method_UnaryCall, serviceImpl.UnaryCall)
+          .AddMethod(__Method_StreamingOutputCall, serviceImpl.StreamingOutputCall)
+          .AddMethod(__Method_StreamingInputCall, serviceImpl.StreamingInputCall)
+          .AddMethod(__Method_FullDuplexCall, serviceImpl.FullDuplexCall)
+          .AddMethod(__Method_HalfDuplexCall, serviceImpl.HalfDuplexCall).Build();
+    }
+
+    // creates a new client stub
+    public static ITestServiceClient NewStub(Channel channel)
+    {
+      return new TestServiceClient(channel);
+    }
+
+    // creates a new client stub
+    public static ITestServiceClient NewStub(Channel channel, StubConfiguration config)
+    {
+      return new TestServiceClient(channel, config);
+    }
+  }
+}
+#endregion
diff --git a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
index 40f32b5a88f0ad989d6416bdd0b8abd2fd4e8e07..d6ba61ef82aefc50b33ef9df5b87571a958daa08 100644
--- a/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
+++ b/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs
@@ -44,7 +44,7 @@ namespace grpc.testing
     /// <summary>
     /// Implementation of TestService server
     /// </summary>
-    public class TestServiceImpl : TestServiceGrpc.ITestService
+    public class TestServiceImpl : TestService.ITestService
     {
         public Task<Empty> EmptyCall(ServerCallContext context, Empty request)
         {
diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f980787bb70ff2f133e45a6b248f713a2140afbf
--- /dev/null
+++ b/src/csharp/generate_proto_csharp.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Regenerates gRPC service stubs from proto files.
+set +e
+cd $(dirname $0)
+
+PLUGIN=protoc-gen-grpc=../../bins/opt/grpc_csharp_plugin
+EXAMPLES_DIR=Grpc.Examples
+INTEROP_DIR=Grpc.IntegrationTesting
+
+protoc --plugin=$PLUGIN --grpc_out=$EXAMPLES_DIR \
+    -I $EXAMPLES_DIR/proto $EXAMPLES_DIR/proto/math.proto
+
+protoc --plugin=$PLUGIN --grpc_out=$INTEROP_DIR \
+    -I $INTEROP_DIR/proto $INTEROP_DIR/proto/test.proto