avro-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From blachn...@apache.org
Subject [avro] branch master updated: AVRO-2389: Add C# reflection-based serializer/deserializer (#587)
Date Fri, 02 Aug 2019 00:15:12 GMT
This is an automated email from the ASF dual-hosted git repository.

blachniet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new 7293dd0  AVRO-2389: Add C# reflection-based serializer/deserializer (#587)
7293dd0 is described below

commit 7293dd0be192d8e5230f5a1c597c2c7577f91efd
Author: pa009fa <34035378+pa009fa@users.noreply.github.com>
AuthorDate: Thu Aug 1 17:15:06 2019 -0700

    AVRO-2389: Add C# reflection-based serializer/deserializer (#587)
    
    * AVRO-2389: C# reflection based serializer/deserializer
    
    * AvroFieldAttribute - bug renaming field, added C# XML comments
    
    * Update lang/csharp/src/apache/main/Reflect/ArrayHelper.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/ArraySchemaExtensions.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/AvroFieldAttribute.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/ClassCache.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/DateTimeOffsetToLongConverter.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/test/Reflect/TestFixed.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/test/Reflect/TestFromAvroProject.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/test/Reflect/TestLogMessage.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/test/Reflect/TestReflect.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/test/Reflect/TestArray.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/DotnetClass.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/DotnetProperty.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/EnumCache.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/FuncFieldConverter.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/IAvroFieldConverter.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/ReflectDefaultReader.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/ReflectDefaultWriter.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/ReflectReader.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/ReflectWriter.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/main/Reflect/TypedFieldConverter.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/test/Reflect/CompareUtils.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update lang/csharp/src/apache/test/Reflect/TestArray.cs
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Fixed copyright
    
    * Readme
    
    * Update lang/csharp/src/apache/main/Reflect/Readme.md
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * Update NOTICE.txt
    
    Co-Authored-By: Brian Lachniet <blachniet@gmail.com>
    
    * renamed Readme.md
    
    * Renamed Readme.md
---
 .gitignore                                         |   2 +-
 NOTICE.txt                                         |  10 +
 lang/csharp/src/apache/main/Reflect/ArrayHelper.cs |  97 ++++
 .../apache/main/Reflect/ArraySchemaExtensions.cs   |  47 ++
 .../src/apache/main/Reflect/AvroFieldAttribute.cs  |  69 +++
 lang/csharp/src/apache/main/Reflect/ClassCache.cs  | 261 ++++++++++
 .../main/Reflect/DateTimeOffsetToLongConverter.cs  |  70 +++
 lang/csharp/src/apache/main/Reflect/DotnetClass.cs | 148 ++++++
 .../src/apache/main/Reflect/DotnetProperty.cs      | 140 ++++++
 lang/csharp/src/apache/main/Reflect/EnumCache.cs   |  57 +++
 .../src/apache/main/Reflect/FuncFieldConverter.cs  |  68 +++
 .../src/apache/main/Reflect/IAvroFieldConverter.cs |  57 +++
 lang/csharp/src/apache/main/Reflect/README.md      | 198 ++++++++
 .../apache/main/Reflect/ReflectDefaultReader.cs    | 546 +++++++++++++++++++++
 .../apache/main/Reflect/ReflectDefaultWriter.cs    | 208 ++++++++
 .../src/apache/main/Reflect/ReflectReader.cs       |  93 ++++
 .../src/apache/main/Reflect/ReflectWriter.cs       |  73 +++
 .../src/apache/main/Reflect/TypedFieldConverter.cs |  97 ++++
 .../csharp/src/apache/test/Reflect/CompareUtils.cs |  50 ++
 lang/csharp/src/apache/test/Reflect/TestArray.cs   | 247 ++++++++++
 lang/csharp/src/apache/test/Reflect/TestFixed.cs   | 162 ++++++
 .../src/apache/test/Reflect/TestFromAvroProject.cs | 369 ++++++++++++++
 .../src/apache/test/Reflect/TestLogMessage.cs      | 113 +++++
 lang/csharp/src/apache/test/Reflect/TestReflect.cs | 170 +++++++
 24 files changed, 3351 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index 19b5616..4c64c41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,5 +18,5 @@ test-output
 /lang/java/compiler/nb-configuration.xml
 /lang/java/compiler/nbproject/
 **/.vscode/**/*
+.DS_Store
 .factorypath
-
diff --git a/NOTICE.txt b/NOTICE.txt
index 7a43bb8..737629b 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -63,3 +63,13 @@ Apache Log4Net includes the following in its NOTICE file:
 |
 | This product includes software developed at
 | The Apache Software Foundation (https://www.apache.org/).
+
+csharp reflect serializers were contributed by Pitney Bowes Inc.
+
+| Copyright 2019 Pitney Bowes Inc.
+| Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
+| You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0.
+| Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, 
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+| See the License for the specific language governing permissions and limitations under the License.
+
diff --git a/lang/csharp/src/apache/main/Reflect/ArrayHelper.cs b/lang/csharp/src/apache/main/Reflect/ArrayHelper.cs
new file mode 100644
index 0000000..5cd1437
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/ArrayHelper.cs
@@ -0,0 +1,97 @@
+/*  
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Class to help serialize and deserialize arrays. Arrays need the following methods Count(), Add(), Clear().true
+    /// This class allows these methods to be specified externally to the collection.
+    /// </summary>
+    public class ArrayHelper
+    {
+        private static Type _defaultType = typeof(List<>);
+
+        /// <summary>
+        /// Collection type to apply by default to all array objects. If not set this defaults to a generic List.
+        /// </summary>
+        /// <value></value>
+        public static Type DefaultType
+        {
+            get => _defaultType;
+            set => _defaultType = value;
+        }
+
+        /// <summary>
+        /// The array
+        /// </summary>
+        /// <value></value>
+        public IEnumerable Enumerable { get; set; }
+
+        /// <summary>
+        /// Return the number of elements in the array.
+        /// </summary>
+        /// <value></value>
+        public virtual int Count()
+        {
+            IList e = (IList)Enumerable;
+            return e.Count;
+        }
+
+        /// <summary>
+        /// Add an element to the array.
+        /// </summary>
+        /// <value></value>
+        public virtual void Add(object o)
+        {
+            IList e = (IList)Enumerable;
+            e.Add(o);
+        }
+
+        /// <summary>
+        /// Clear the array.
+        /// </summary>
+        /// <value></value>
+        public virtual void Clear()
+        {
+            IList e = (IList)Enumerable;
+            e.Clear();
+        }
+
+        /// <summary>
+        /// Type of the array to create when deserializing
+        /// </summary>
+        /// <value></value>
+        public virtual Type ArrayType
+        {
+            get => _defaultType;
+        }
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        public ArrayHelper(IEnumerable enumerable)
+        {
+            Enumerable = enumerable;
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/ArraySchemaExtensions.cs b/lang/csharp/src/apache/main/Reflect/ArraySchemaExtensions.cs
new file mode 100644
index 0000000..33ae5f5
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/ArraySchemaExtensions.cs
@@ -0,0 +1,47 @@
+/*  
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Extension methods for ArraySchema - make helper metadata look more like a property
+    /// </summary>
+    public static class ArraySchemaExtensions
+    {
+        /// <summary>
+        /// Return the name of the array helper
+        /// </summary>
+        /// <param name="ars">this</param>
+        /// <returns>value of the helper metadata - null if it isnt present</returns>
+        public static string GetHelper(this ArraySchema ars)
+        {
+            string s = null;
+            s = ars.GetProperty("helper");
+            if (s != null && s.Length > 2)
+            {
+                s = s.Substring(1, s.Length - 2);
+            }
+            else
+            {
+                s = null;
+            }
+
+            return s;
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/AvroFieldAttribute.cs b/lang/csharp/src/apache/main/Reflect/AvroFieldAttribute.cs
new file mode 100644
index 0000000..ccf56b1
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/AvroFieldAttribute.cs
@@ -0,0 +1,69 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Reflection;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Attribute that specifies the mapping between an Avro field and C# class property.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
+    public class AvroFieldAttribute : Attribute
+    {
+        /// <summary>
+        /// Name of the field in the Avro Schema
+        /// </summary>
+        /// <value></value>
+        public string FieldName { get; set; }
+
+        /// <summary>
+        /// Convert the property into a standard Avro type - e.g. DateTimeOffset to long
+        /// </summary>
+        /// <value></value>
+        public IAvroFieldConverter Converter { get; set; }
+
+        /// <summary>
+        /// Attribute to hold a field name and optionally a converter
+        /// </summary>
+        /// <param name="fieldName"></param>
+        /// <param name="converter"></param>
+        public AvroFieldAttribute(string fieldName, Type converter = null)
+        {
+            FieldName = fieldName;
+            if (converter != null)
+            {
+                Converter = (IAvroFieldConverter)Activator.CreateInstance(converter);
+            }
+        }
+
+        /// <summary>
+        /// Used in property name mapping to specify a property type converter for the attribute.
+        /// </summary>
+        /// <param name="converter"></param>
+        public AvroFieldAttribute(Type converter)
+        {
+            FieldName = null;
+            if (converter != null)
+            {
+                Converter = (IAvroFieldConverter)Activator.CreateInstance(converter);
+            }
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/ClassCache.cs b/lang/csharp/src/apache/main/Reflect/ClassCache.cs
new file mode 100644
index 0000000..22b56c4
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/ClassCache.cs
@@ -0,0 +1,261 @@
+/*  
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Reflection;
+using Avro;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Class holds a cache of C# classes and their properties. The key for the cache is the schema full name.
+    /// </summary>
+    public class ClassCache
+    {
+        private static ConcurrentBag<IAvroFieldConverter> _defaultConverters = new ConcurrentBag<IAvroFieldConverter>();
+
+        private ConcurrentDictionary<string, DotnetClass> _nameClassMap = new ConcurrentDictionary<string, DotnetClass>();
+
+        private ConcurrentDictionary<string, Type> _nameArrayMap = new ConcurrentDictionary<string, Type>();
+
+        private void AddClassNameMapItem(RecordSchema schema, Type dotnetClass)
+        {
+            if (schema != null && GetClass(schema) != null)
+            {
+                return;
+            }
+
+            if (!dotnetClass.IsClass)
+            {
+                throw new AvroException($"Type {dotnetClass.Name} is not a class");
+            }
+
+            _nameClassMap.TryAdd(schema.Fullname, new DotnetClass(dotnetClass, schema, this));
+        }
+
+        /// <summary>
+        /// Add a default field converter
+        /// </summary>
+        /// <param name="converter"></param>
+        public static void AddDefaultConverter(IAvroFieldConverter converter)
+        {
+            _defaultConverters.Add(converter);
+        }
+
+        /// <summary>
+        /// Add a converter defined using Func&lt;&gt;. The converter will be used whenever the source and target types
+        /// match and a specific attribute is not defined.
+        /// </summary>
+        /// <param name="from"></param>
+        /// <param name="to"></param>
+        /// <typeparam name="A"></typeparam>
+        /// <typeparam name="P"></typeparam>
+        public static void AddDefaultConverter<A, P>(Func<A, Schema, P> from, Func<P, Schema, A> to)
+        {
+            _defaultConverters.Add(new FuncFieldConverter<A, P>(from, to));
+        }
+
+        /// <summary>
+        /// Find a default converter
+        /// </summary>
+        /// <param name="tag"></param>
+        /// <param name="propType"></param>
+        /// <returns>The first matching converter - null if there isnt one</returns>
+        public IAvroFieldConverter GetDefaultConverter(Avro.Schema.Type tag, Type propType)
+        {
+            Type avroType;
+            switch (tag)
+            {
+                case Avro.Schema.Type.Null:
+                    return null;
+                case Avro.Schema.Type.Boolean:
+                    avroType = typeof(bool);
+                    break;
+                case Avro.Schema.Type.Int:
+                    avroType = typeof(int);
+                    break;
+                case Avro.Schema.Type.Long:
+                    avroType = typeof(long);
+                    break;
+                case Avro.Schema.Type.Float:
+                    avroType = typeof(float);
+                    break;
+                case Avro.Schema.Type.Double:
+                    avroType = typeof(double);
+                    break;
+                case Avro.Schema.Type.Bytes:
+                    avroType = typeof(byte[]);
+                    break;
+                case Avro.Schema.Type.String:
+                    avroType = typeof(string);
+                    break;
+                case Avro.Schema.Type.Record:
+                    return null;
+                case Avro.Schema.Type.Enumeration:
+                    return null;
+                case Avro.Schema.Type.Array:
+                    return null;
+                case Avro.Schema.Type.Map:
+                    return null;
+                case Avro.Schema.Type.Union:
+                    return null;
+                case Avro.Schema.Type.Fixed:
+                    avroType = typeof(byte[]);
+                    break;
+                case Avro.Schema.Type.Error:
+                    return null;
+                default:
+                    return null;
+            }
+
+            foreach (var c in _defaultConverters)
+            {
+                if (c.GetAvroType() == avroType && c.GetPropertyType() == propType)
+                {
+                    return c;
+                }
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Add an array helper. Array helpers are used for collections that are not generic lists.
+        /// </summary>
+        /// <param name="name">Name of the helper. Corresponds to metadata "helper" field in the schema.</param>
+        /// <param name="helperType">Type of helper. Inherited from ArrayHelper</param>
+        public void AddArrayHelper(string name, Type helperType)
+        {
+            if (!typeof(ArrayHelper).IsAssignableFrom(helperType))
+            {
+                throw new AvroException($"{helperType.Name} is not an ArrayHelper");
+            }
+
+            _nameArrayMap.TryAdd(name, helperType);
+        }
+
+        /// <summary>
+        /// Find an array helper for an array schema node.
+        /// </summary>
+        /// <param name="schema">Schema</param>
+        /// <param name="enumerable">The array object. If it is null then Add(), Count() and Clear methods will throw exceptions.</param>
+        /// <returns></returns>
+        public ArrayHelper GetArrayHelper(ArraySchema schema, IEnumerable enumerable)
+        {
+            Type h;
+            // note ArraySchema is unamed and doesnt have a FulllName, use "helper" metadata
+            // metadata is json string, strip quotes
+            string s = null;
+            s = schema.GetHelper();
+
+            if (s != null && _nameArrayMap.TryGetValue(s, out h))
+            {
+                return (ArrayHelper)Activator.CreateInstance(h, enumerable);
+            }
+
+            return (ArrayHelper)Activator.CreateInstance(typeof(ArrayHelper), enumerable);
+        }
+
+        /// <summary>
+        /// Find a class that matches the schema full name.
+        /// </summary>
+        /// <param name="schema"></param>
+        /// <returns></returns>
+        public DotnetClass GetClass(RecordSchema schema)
+        {
+            DotnetClass c;
+            if (!_nameClassMap.TryGetValue(schema.Fullname, out c))
+            {
+               return null;
+            }
+
+            return c;
+        }
+
+        /// <summary>
+        /// Add an entry to the class cache.
+        /// </summary>
+        /// <param name="objType">Type of the C# class</param>
+        /// <param name="s">Schema</param>
+        public void LoadClassCache(Type objType, Schema s)
+        {
+            switch (s)
+            {
+                case RecordSchema rs:
+                    if (!objType.IsClass)
+                    {
+                        throw new AvroException($"Cant map scalar type {objType.Name} to record {rs.Fullname}");
+                    }
+
+                    if (typeof(byte[]).IsAssignableFrom(objType)
+                        || typeof(string).IsAssignableFrom(objType)
+                        || typeof(IEnumerable).IsAssignableFrom(objType)
+                        || typeof(IDictionary).IsAssignableFrom(objType))
+                    {
+                        throw new AvroException($"Cant map type {objType.Name} to record {rs.Fullname}");
+                    }
+
+                    AddClassNameMapItem(rs, objType);
+                    var c = GetClass(rs);
+                    foreach (var f in rs.Fields)
+                    {
+                        var t = c.GetPropertyType(f);
+                        LoadClassCache(t, f.Schema);
+                    }
+
+                    break;
+                case ArraySchema ars:
+                    if (!typeof(IEnumerable).IsAssignableFrom(objType))
+                    {
+                        throw new AvroException($"Cant map type {objType.Name} to array {ars.Name}");
+                    }
+
+                    if (!objType.IsGenericType)
+                    {
+                        throw new AvroException($"{objType.Name} needs to be a generic type");
+                    }
+
+                    LoadClassCache(objType.GenericTypeArguments[0], ars.ItemSchema);
+                    break;
+                case MapSchema ms:
+                    if (!typeof(IDictionary).IsAssignableFrom(objType))
+                    {
+                        throw new AvroException($"Cant map type {objType.Name} to map {ms.Name}");
+                    }
+
+                    if (!objType.IsGenericType)
+                    {
+                        throw new AvroException($"Cant map non-generic type {objType.Name} to map {ms.Name}");
+                    }
+
+                    if (!typeof(string).IsAssignableFrom(objType.GenericTypeArguments[0]))
+                    {
+                        throw new AvroException($"First type parameter of {objType.Name} must be assignable to string");
+                    }
+
+                    LoadClassCache(objType.GenericTypeArguments[1], ms.ValueSchema);
+                    break;
+                case NamedSchema ns:
+                    EnumCache.AddEnumNameMapItem(ns, objType);
+                    break;
+            }
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/DateTimeOffsetToLongConverter.cs b/lang/csharp/src/apache/main/Reflect/DateTimeOffsetToLongConverter.cs
new file mode 100644
index 0000000..9ed8f57
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/DateTimeOffsetToLongConverter.cs
@@ -0,0 +1,70 @@
+/*  
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Convert C# DateTimeOffset properties to long unix time
+    /// </summary>
+    public class DateTimeOffsetToLongConverter : IAvroFieldConverter
+    {
+        /// <summary>
+        /// Convert from DateTimeOffset to Unix long
+        /// </summary>
+        /// <param name="o">DateTimeOffset</param>
+        /// <param name="s">Schema</param>
+        /// <returns></returns>
+        public object ToAvroType(object o, Schema s)
+        {
+            var dt = (DateTimeOffset)o;
+            return dt.ToUnixTimeMilliseconds();
+        }
+
+        /// <summary>
+        /// Convert from Unix long to DateTimeOffset
+        /// </summary>
+        /// <param name="o">long</param>
+        /// <param name="s">Schema</param>
+        /// <returns></returns>
+        public object FromAvroType(object o, Schema s)
+        {
+            var dt = DateTimeOffset.FromUnixTimeMilliseconds((long)o);
+            return dt;
+        }
+
+        /// <summary>
+        /// Avro type
+        /// </summary>
+        /// <returns></returns>
+        public Type GetAvroType()
+        {
+            return typeof(long);
+        }
+
+        /// <summary>
+        /// Property type
+        /// </summary>
+        /// <returns></returns>
+        public Type GetPropertyType()
+        {
+            return typeof(DateTimeOffset);
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/DotnetClass.cs b/lang/csharp/src/apache/main/Reflect/DotnetClass.cs
new file mode 100644
index 0000000..5bef040
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/DotnetClass.cs
@@ -0,0 +1,148 @@
+/*  
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Reflection;
+using System.Collections.Concurrent;
+using Avro;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Collection of DotNetProperty objects to repre
+    /// </summary>
+    public class DotnetClass
+    {
+        private ConcurrentDictionary<string, DotnetProperty> _propertyMap = new ConcurrentDictionary<string, DotnetProperty>();
+
+        private Type _type;
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="t">type of the class</param>
+        /// <param name="r">record schema</param>
+        /// <param name="cache">class cache - can be reused</param>
+        public DotnetClass(Type t, RecordSchema r, ClassCache cache)
+        {
+            _type = t;
+            foreach (var f in r.Fields)
+            {
+                bool hasAttribute = false;
+                PropertyInfo prop = GetPropertyInfo(f);
+
+                foreach (var attr in prop.GetCustomAttributes(true))
+                {
+                    var avroAttr = attr as AvroFieldAttribute;
+                    if (avroAttr != null)
+                    {
+                        hasAttribute = true;
+                        _propertyMap.TryAdd(f.Name, new DotnetProperty(prop, f.Schema.Tag, avroAttr.Converter, cache));
+                        break;
+                    }
+                }
+
+                if (!hasAttribute)
+                {
+                    _propertyMap.TryAdd(f.Name, new DotnetProperty(prop, f.Schema.Tag, cache));
+                }
+            }
+        }
+
+        private PropertyInfo GetPropertyInfo(Field f)
+        {
+            var prop = _type.GetProperty(f.Name);
+            if (prop != null)
+            {
+                return prop;
+            }
+            foreach (var p in _type.GetProperties())
+            {
+                foreach (var attr in p.GetCustomAttributes(true))
+                {
+                    var avroAttr = attr as AvroFieldAttribute;
+                    if (avroAttr != null && avroAttr.FieldName != null && avroAttr.FieldName == f.Name)
+                    {
+                        return p;
+                    }
+                }
+            }
+
+            throw new AvroException($"Class {_type.Name} doesnt contain property {f.Name}");
+        }
+
+        /// <summary>
+        /// Return the value of a property from an object referenced by a field
+        /// </summary>
+        /// <param name="o">the object</param>
+        /// <param name="f">FieldSchema used to look up the property</param>
+        /// <returns></returns>
+        public object GetValue(object o, Field f)
+        {
+            DotnetProperty p;
+            if (!_propertyMap.TryGetValue(f.Name, out p))
+            {
+                throw new AvroException($"ByPosClass doesnt contain property {f.Name}");
+            }
+
+            return p.GetValue(o, f.Schema);
+        }
+
+        /// <summary>
+        /// Set the value of a property in a C# object
+        /// </summary>
+        /// <param name="o">the object</param>
+        /// <param name="f">field schema</param>
+        /// <param name="v">value for the proprty referenced by the field schema</param>
+        public void SetValue(object o, Field f, object v)
+        {
+            DotnetProperty p;
+            if (!_propertyMap.TryGetValue(f.Name, out p))
+            {
+                throw new AvroException($"ByPosClass doesnt contain property {f.Name}");
+            }
+
+            p.SetValue(o, v, f.Schema);
+        }
+
+        /// <summary>
+        /// Return the type of the Class
+        /// </summary>
+        /// <returns>The </returns>
+        public Type GetClassType()
+        {
+            return _type;
+        }
+
+        /// <summary>
+        /// Return the type of a property referenced by a field
+        /// </summary>
+        /// <param name="f"></param>
+        /// <returns></returns>
+        public Type GetPropertyType(Field f)
+        {
+            DotnetProperty p;
+            if (!_propertyMap.TryGetValue(f.Name, out p))
+            {
+                throw new AvroException($"ByPosClass doesnt contain property {f.Name}");
+            }
+
+            return p.GetPropertyType();
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/DotnetProperty.cs b/lang/csharp/src/apache/main/Reflect/DotnetProperty.cs
new file mode 100644
index 0000000..4ddcdc6
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/DotnetProperty.cs
@@ -0,0 +1,140 @@
+/*  
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Reflection;
+using System.Collections;
+
+namespace Avro.Reflect
+{
+    internal class DotnetProperty
+    {
+        private PropertyInfo _property;
+
+        public IAvroFieldConverter Converter { get; set; }
+
+        private bool IsPropertyCompatible(Avro.Schema.Type schemaTag)
+        {
+            Type propType;
+
+            if (Converter == null)
+            {
+                propType = _property.PropertyType;
+            }
+            else
+            {
+                propType = Converter.GetAvroType();
+            }
+
+            switch (schemaTag)
+            {
+                case Avro.Schema.Type.Null:
+                    return (Nullable.GetUnderlyingType(propType) != null) || (!propType.IsValueType);
+                case Avro.Schema.Type.Boolean:
+                    return propType == typeof(bool);
+                case Avro.Schema.Type.Int:
+                    return propType == typeof(int);
+                case Avro.Schema.Type.Long:
+                    return propType == typeof(long);
+                case Avro.Schema.Type.Float:
+                    return propType == typeof(float);
+                case Avro.Schema.Type.Double:
+                    return propType == typeof(double);
+                case Avro.Schema.Type.Bytes:
+                    return propType == typeof(byte[]);
+                case Avro.Schema.Type.String:
+                    return typeof(string).IsAssignableFrom(propType);
+                case Avro.Schema.Type.Record:
+                    //TODO: this probably should work for struct too
+                    return propType.IsClass;
+                case Avro.Schema.Type.Enumeration:
+                    return propType.IsEnum;
+                case Avro.Schema.Type.Array:
+                    return typeof(IEnumerable).IsAssignableFrom(propType);
+                case Avro.Schema.Type.Map:
+                    return typeof(IDictionary).IsAssignableFrom(propType);
+                case Avro.Schema.Type.Union:
+                    return true;
+                case Avro.Schema.Type.Fixed:
+                    return propType == typeof(byte[]);
+                case Avro.Schema.Type.Error:
+                    return propType.IsClass;
+            }
+
+            return false;
+        }
+
+        public DotnetProperty(PropertyInfo property, Avro.Schema.Type schemaTag,  IAvroFieldConverter converter, ClassCache cache)
+        {
+            _property = property;
+            Converter = converter;
+
+            if (!IsPropertyCompatible(schemaTag))
+            {
+                if (Converter == null)
+                {
+                    var c = cache.GetDefaultConverter(schemaTag, _property.PropertyType);
+                    if (c != null)
+                    {
+                        Converter = c;
+                        return;
+                    }
+                }
+
+                throw new AvroException($"Property {property.Name} in object {property.DeclaringType} isn't compatible with Avro schema type {schemaTag}");
+            }
+        }
+
+        public DotnetProperty(PropertyInfo property, Avro.Schema.Type schemaTag, ClassCache cache)
+            : this(property, schemaTag, null, cache)
+        {
+        }
+
+        public virtual Type GetPropertyType()
+        {
+            if (Converter != null)
+            {
+                return Converter.GetAvroType();
+            }
+
+            return _property.PropertyType;
+        }
+
+        public virtual object GetValue(object o, Schema s)
+        {
+            if (Converter != null)
+            {
+                return Converter.ToAvroType(_property.GetValue(o), s);
+            }
+
+            return _property.GetValue(o);
+        }
+
+        public virtual void SetValue(object o, object v, Schema s)
+        {
+            if (Converter != null)
+            {
+                _property.SetValue(o, Converter.FromAvroType(v, s));
+            }
+            else
+            {
+                _property.SetValue(o, v);
+            }
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/EnumCache.cs b/lang/csharp/src/apache/main/Reflect/EnumCache.cs
new file mode 100644
index 0000000..7fbfc99
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/EnumCache.cs
@@ -0,0 +1,57 @@
+/*  
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Collections.Concurrent;
+using Avro;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Cache of enum types. Cache key is the schema fullname.
+    /// </summary>
+    public static class EnumCache
+    {
+        private static ConcurrentDictionary<string, Type> _nameEnumMap = new ConcurrentDictionary<string, Type>();
+
+        /// <summary>
+        /// Add and entry to the cache
+        /// </summary>
+        /// <param name="schema"></param>
+        /// <param name="dotnetEnum"></param>
+        public static void AddEnumNameMapItem(NamedSchema schema, Type dotnetEnum)
+        {
+            _nameEnumMap.TryAdd(schema.Fullname, dotnetEnum);
+        }
+
+        /// <summary>
+        /// Lookup an entry in the cache - based on the schema fullname
+        /// </summary>
+        /// <param name="schema"></param>
+        /// <returns></returns>
+        public static Type GetEnumeration(NamedSchema schema)
+        {
+            Type t;
+            if (!_nameEnumMap.TryGetValue(schema.Fullname, out t))
+            {
+                throw new AvroException($"Couldnt find enumeration for avro fullname: {schema.Fullname}");
+            }
+
+            return t;
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/FuncFieldConverter.cs b/lang/csharp/src/apache/main/Reflect/FuncFieldConverter.cs
new file mode 100644
index 0000000..3914888
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/FuncFieldConverter.cs
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using Avro.Generic;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Field converter using a Func
+    /// </summary>
+    /// <typeparam name="A">Avro type</typeparam>
+    /// <typeparam name="P">Property type</typeparam>
+    public class FuncFieldConverter<A, P> : TypedFieldConverter<A, P>
+    {
+        /// <summary>
+        ///
+        /// </summary>
+        /// <param name="from">Delegate to convert from C# type to Avro type</param>
+        /// <param name="to">Delegate to convert from Avro type to C# type</param>
+        public FuncFieldConverter(Func<A, Schema, P> from, Func<P, Schema, A> to)
+        {
+            _from = from;
+            _to = to;
+        }
+
+        private Func<A, Schema, P> _from;
+
+        private Func<P, Schema, A> _to;
+
+        /// <summary>
+        /// Inherited conversion method - call the Func.
+        /// </summary>
+        /// <param name="o"></param>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public override P From(A o, Schema s)
+        {
+            return _from(o, s);
+        }
+
+        /// <summary>
+        /// Inherited conversion method - call the Func.
+        /// </summary>
+        /// <param name="o"></param>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public override A To(P o, Schema s)
+        {
+            return _to(o, s);
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/IAvroFieldConverter.cs b/lang/csharp/src/apache/main/Reflect/IAvroFieldConverter.cs
new file mode 100644
index 0000000..c384445
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/IAvroFieldConverter.cs
@@ -0,0 +1,57 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ using System;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Converters can be added to properties with an AvroField attribute. Converters convert between the
+    /// property type and the avro type.
+    /// </summary>
+    public interface IAvroFieldConverter
+    {
+        /// <summary>
+        /// Convert from the C# type to the avro type
+        /// </summary>
+        /// <param name="o">Value to convert</param>
+        /// <param name="s">Schema</param>
+        /// <returns>Converted value</returns>
+        object ToAvroType(object o, Schema s);
+
+        /// <summary>
+        /// Convert from the avro type to the C# type
+        /// </summary>
+        /// <param name="o">Value to convert</param>
+        /// <param name="s">Schema</param>
+        /// <returns>Converted value</returns>
+        object FromAvroType(object o, Schema s);
+
+        /// <summary>
+        /// Avro type
+        /// </summary>
+        /// <returns></returns>
+        Type GetAvroType();
+
+        /// <summary>
+        /// Property type
+        /// </summary>
+        /// <returns></returns>
+        Type GetPropertyType();
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/README.md b/lang/csharp/src/apache/main/Reflect/README.md
new file mode 100644
index 0000000..dfb4836
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/README.md
@@ -0,0 +1,198 @@
+# Namespace Avro.Reflect
+
+This namespace contains classes that implement Avro serialization and deserialization for plain C# objects. The classes use .net reflection to implement the serializers. The interface is similar to the Generic and Specific serialiation classes.
+
+## Serialization
+
+The approach starts with the schema and interates both the schema and the dotnet object together in a depth first manner per the specification. Serialization is the same as the Generic serializer except where the serializer encounters:
+- *A fixed type*: if the corresponding dotnet object type is a byte[] of the correct length then the object is serialized, otherwise an exception is thrown.
+- *A record type*: the serializer matches the schema property name to the dotnet object property name and then reursively serializes the schema property and the dotnet object property
+- *An array type*: See array serialization/deserialization.
+
+Basic serialization is performed as in the following example:
+
+```csharp
+    Schema schema; // created previously
+    T myObject; // created previously
+
+
+    var avroWriter = new ReflectWriter<T>(schema);
+    using (var stream = new MemoryStream(256))
+    {
+        avroWriter.Write(myObject, new BinaryEncoder(stream));
+    }
+```
+
+## Deserialization
+
+Deserialization proceeds in much the same fashion as serialization. When required objects are created. By default this is with:
+```csharp
+    Activator.CreateInstance(x);
+```
+however this can be overridden by setting the deserializer property RecordFactory. 
+```csharp
+public Func<Type, object> RecordFactory {get;set;}
+```
+You might want to do this if your class contains interfaces and/or if you use an IoC container.
+
+See the section on Arrays. The ArrayHelper specifies the type of object created when an array is deserialized. The default is List<T>.
+
+The type created for Map objects is specified by the Deserializer property MapType. *This must be a two (or more) parameter generic type where the first type paramater is string and the second is undefined* e.g. List<string,>. 
+```csharp
+public Type MapType { get; set; }
+```
+By default the MapType is List<string,>
+```
+
+Basic deserialization is performed as in the following example:
+
+```csharp
+    Schema schema; // created previously
+
+    // using same writer and reader schema in this example.
+    var avroReader = new ReflectReader<T>(schema, schema);
+
+    using (var stream = new MemoryStream(serialized))
+    {
+        deserialized = avroReader.Read(null, new BinaryDecoder(stream));
+    }
+```
+
+## Class cache
+
+The dotnet reflection libraries can add an amount of performance overhead. Efforts are made to minimize this by supporting a cache of class details obtained by reflection (PropertyInfo objects) so that property value lookups can be performed quickly and with as little overhead as possible. 
+
+The class cache can be created separately from the serializer/deserializer and reused.
+
+```csharp
+    var cache = new ClassCache();
+    var writer = new ReflectWriter<MultiList>(schema, cache);
+    var reader = new ReflectReader<MultiList>(schema, schema, cache);
+```
+The class cache is also used with default type conversions and with array serialization and deserialization.
+
+## Converters
+
+Converters are classes that convert to and from Avro primitive types and dotnet types. An example of where converters are used is to convert between dotnet DateTimeOffet object and the chosen Avro primitive. 
+
+Converters are implemented by inheriting from TypedFieldConverter<byte[],GenericFixed>, or creating an object of type FuncFieldConverter<A,T>.
+
+_Example TypedFieldConverter_:
+
+```csharp
+        public class GenericFixedConverter : TypedFieldConverter<byte[],GenericFixed>
+        {
+            public override GenericFixed From(byte[] o, Schema s)
+            {
+                return new GenericFixed(s as FixedSchema, o);
+            }
+
+            public override byte[] To(GenericFixed o, Schema s)
+            {
+                return o.Value;
+            }
+        }
+```
+
+### Specifying Converters in Attributes
+
+```csharp
+    public class LogMessage
+    {
+
+        [AvroField(typeof(DateTimeOffsetToLongConverter))]
+        public DateTimeOffset TimeStamp { get; set; }
+
+    }
+```
+
+### Default Converters
+
+Default converters are defined to convert between an Avro primitive and C# type without explicitly defining the converter for a field. Default converters are static and are registered with the class cache.
+
+```csharp
+    ClassCache.AddDefaultConverter<byte[], GenericFixed>((a,s)=>new GenericFixed(s as FixedSchema, a), (p,s)=>p.Value);
+    var writer = new ReflectWriter<GenericFixedRec>(schema);
+    var reader = new ReflectReader<GenericFixedRec>(schema, schema);
+
+```
+## Attributes
+
+The AvroField attribute can be used to defined field converters or to change the name of the dotnet property (or both).
+
+```csharp
+    public class LogMessage
+    {
+        [AvroField("message")]
+        public string Message { get; set; }
+
+        [AvroField(typeof(DateTimeOffsetToLongConverter))]
+        public DateTimeOffset TimeStamp { get; set; }
+    }
+```
+
+## Arrays
+
+By default the reflect code will serialize and deserialized between Avro arrays and classes that implement IList. Classes that implement IEnumerable but do not implement IList can be handled by implementing an ArrayHelper class. The array helper provides a standard interface for a number of methods needed for serialization and deserialization but which are not supported by IEnumerable.
+
+An additional metadata called "helper" is required in the schema. This acts like the name of a record type and is used to associate a helper with a particular schema array. 
+
+_Example_: ConcurrentQueue
+
+
+```csharp
+
+    public class ConcurrentQueueHelper<T> : ArrayHelper
+    {
+        public override int Count()
+        {
+            ConcurrentQueue<T> e = (ConcurrentQueue<T>)Enumerable;
+            return e.Count;
+        }
+
+        public override void Add(object o)
+        {
+            ConcurrentQueue<T> e = (ConcurrentQueue<T>)Enumerable;
+            e.Enqueue((T)o);
+        }
+
+        public override void Clear()
+        {
+            ConcurrentQueue<T> e = (ConcurrentQueue<T>)Enumerable;
+#if NET461
+            while (e.TryDequeue(out _)) { }
+#else
+            e.Clear();
+#endif
+        }
+
+        public override Type ArrayType
+        {
+            get => typeof(ConcurrentQueue<>);
+        }
+
+        public ConcurrentQueueHelper(IEnumerable enumerable) : base(enumerable)
+        {
+            Enumerable = enumerable;
+        }
+    }
+
+    string recordList = @"
+    {
+        ""type"": ""array"",
+        ""helper"": ""recordListQueue"",
+        ""items"": ""string""
+    }"
+
+    // using the helper
+
+    var schema = Schema.Parse(recordList);
+    var fixedRecWrite = new ConcurrentQueue<string>();
+
+    var cache = new ClassCache();
+    cache.AddArrayHelper("recordListQueue", typeof(ConcurrentQueueHelper<string>));
+
+    var writer = new ReflectWriter<ConcurrentQueue<ConcurrentQueueRec>>(schema, cache);
+    var reader = new ReflectReader<ConcurrentQueue<ConcurrentQueueRec>>(schema, schema, cache);
+
+```
diff --git a/lang/csharp/src/apache/main/Reflect/ReflectDefaultReader.cs b/lang/csharp/src/apache/main/Reflect/ReflectDefaultReader.cs
new file mode 100644
index 0000000..9e6e09c
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/ReflectDefaultReader.cs
@@ -0,0 +1,546 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Avro.IO;
+using Avro.Specific;
+using Newtonsoft.Json.Linq;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Reader class for reading data and storing into specific classes
+    /// </summary>
+    public class ReflectDefaultReader : SpecificDefaultReader
+    {
+        /// <summary>
+        /// C# type to create when deserializing a map. Must implement IDictionary&lt;,&gt; and the first
+        /// type parameter must be a string. Default is System.Collections.Generic.Dictionary
+        /// </summary>
+        /// <value></value>
+        public Type MapType { get => _mapType; set => _mapType = value; }
+
+        private ClassCache _classCache = new ClassCache();
+
+        /// <summary>
+        /// Class cache
+        /// </summary>
+        /// <value></value>
+        public ClassCache ClassCache { get => _classCache; }
+
+        private Type _mapType = typeof(Dictionary<,>);
+
+        private Func<Type, object> _recordFactory = x => Activator.CreateInstance(x);
+
+        /// <summary>
+        /// Delegate to a factory method to create objects of type x. If you are deserializing to interfaces
+        /// you could use an IoC container factory insread of the default. Default is Activator.CreateInstance()
+        /// </summary>
+        /// <returns></returns>
+        public Func<Type, object> RecordFactory { get => _recordFactory; set => _recordFactory = value; }
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="objType"></param>
+        /// <param name="writerSchema"></param>
+        /// <param name="readerSchema"></param>
+        /// <param name="cache"></param>
+        public ReflectDefaultReader(Type objType, Schema writerSchema, Schema readerSchema, ClassCache cache)
+            : base(writerSchema, readerSchema)
+        {
+            if (cache != null)
+            {
+                _classCache = cache;
+            }
+
+            _classCache.LoadClassCache(objType, readerSchema);
+        }
+
+        /// <summary>
+        /// Gets the string representation of the schema's data type
+        /// </summary>
+        /// <param name="schema">schema</param>
+        /// <param name="nullable">flag to indicate union with null</param>
+        /// <returns></returns>
+        internal Type GetTypeFromSchema(Schema schema, bool nullable)
+        {
+            switch (schema.Tag)
+            {
+                case Schema.Type.Null:
+                    return typeof(object);
+
+                case Schema.Type.Boolean:
+                    return nullable ? typeof(bool?) : typeof(bool);
+
+                case Schema.Type.Int:
+                    return nullable ? typeof(int?) : typeof(int);
+
+                case Schema.Type.Long:
+                    return nullable ? typeof(long?) : typeof(long);
+
+                case Schema.Type.Float:
+                    return nullable ? typeof(float?) : typeof(float);
+
+                case Schema.Type.Double:
+                    return nullable ? typeof(double?) : typeof(double);
+
+                case Schema.Type.Fixed:
+                case Schema.Type.Bytes:
+                    return typeof(byte[]);
+
+                case Schema.Type.String:
+                    return typeof(string);
+
+                case Schema.Type.Enumeration:
+                    var namedSchema = schema as NamedSchema;
+                    if (namedSchema == null)
+                    {
+                        throw new Exception("Unable to cast schema into a named schema");
+                    }
+
+                    Type enumType = null;
+                    enumType = EnumCache.GetEnumeration(namedSchema);
+                    if (enumType == null)
+                    {
+                        throw new Exception(string.Format("Couldn't find type matching enum name {0}", namedSchema.Fullname));
+                    }
+
+                    if (nullable)
+                    {
+                        return typeof(Nullable<>).MakeGenericType(new Type[] { enumType });
+                    }
+                    else
+                    {
+                        return enumType;
+                    }
+
+                case Schema.Type.Record:
+                case Schema.Type.Error:
+                    var recordSchema = schema as RecordSchema;
+                    if (recordSchema == null)
+                    {
+                        throw new Exception("Unable to cast schema into a named schema");
+                    }
+
+                    Type recordtype = null;
+                    recordtype = _classCache.GetClass(recordSchema).GetClassType();
+                    if (recordtype == null)
+                    {
+                        throw new Exception(string.Format("Couldn't find type matching schema name {0}", recordSchema.Fullname));
+                    }
+
+                    return recordtype;
+
+                case Schema.Type.Array:
+                    var arraySchema = schema as ArraySchema;
+                    if (arraySchema == null)
+                    {
+                        throw new Exception("Unable to cast schema into an array schema");
+                    }
+
+                    var arrayHelper = _classCache.GetArrayHelper(arraySchema, null);
+                    return arrayHelper.ArrayType.MakeGenericType(new Type[] { GetTypeFromSchema(arraySchema.ItemSchema, false) });
+
+                case Schema.Type.Map:
+                    var mapSchema = schema as MapSchema;
+                    if (mapSchema == null)
+                    {
+                        throw new Exception("Unable to cast schema into a map schema");
+                    }
+
+                    return MapType.MakeGenericType(new Type[] { typeof(string), GetTypeFromSchema(mapSchema.ValueSchema, false) });
+
+                case Schema.Type.Union:
+                    var unionSchema = schema as UnionSchema;
+                    if (unionSchema == null)
+                    {
+                        throw new Exception("Unable to cast schema into a union schema");
+                    }
+
+                    Schema nullibleType = CodeGen.getNullableType(unionSchema);
+                    if (nullibleType == null)
+                    {
+                        return typeof(object);
+                    }
+                    else
+                    {
+                        return GetTypeFromSchema(nullibleType, true);
+                    }
+            }
+
+            throw new Exception("Unable to generate CodeTypeReference for " + schema.Name + " type " + schema.Tag);
+        }
+
+        /// <summary>
+        /// Gets the default value for a schema object
+        /// </summary>
+        /// <param name="s"></param>
+        /// <param name="defaultValue"></param>
+        /// <returns></returns>
+        public object GetDefaultValue(Schema s, JToken defaultValue)
+        {
+            if (defaultValue == null)
+            {
+                return null;
+            }
+
+            switch (s.Tag)
+            {
+                case Schema.Type.Boolean:
+                    if (defaultValue.Type != JTokenType.Boolean)
+                    {
+                        throw new AvroException("Default boolean value " + defaultValue.ToString() + " is invalid, expected is json boolean.");
+                    }
+
+                    return (bool)defaultValue;
+
+                case Schema.Type.Int:
+                    if (defaultValue.Type != JTokenType.Integer)
+                    {
+                        throw new AvroException("Default int value " + defaultValue.ToString() + " is invalid, expected is json integer.");
+                    }
+
+                    return Convert.ToInt32((int)defaultValue);
+
+                case Schema.Type.Long:
+                    if (defaultValue.Type != JTokenType.Integer)
+                    {
+                        throw new AvroException("Default long value " + defaultValue.ToString() + " is invalid, expected is json integer.");
+                    }
+
+                    return Convert.ToInt64((long)defaultValue);
+
+                case Schema.Type.Float:
+                    if (defaultValue.Type != JTokenType.Float)
+                    {
+                        throw new AvroException("Default float value " + defaultValue.ToString() + " is invalid, expected is json number.");
+                    }
+
+                    return (float)defaultValue;
+
+                case Schema.Type.Double:
+                    if (defaultValue.Type == JTokenType.Integer)
+                    {
+                        return Convert.ToDouble((int)defaultValue);
+                    }
+                    else if (defaultValue.Type == JTokenType.Float)
+                    {
+                        return Convert.ToDouble((float)defaultValue);
+                    }
+                    else
+                    {
+                        throw new AvroException("Default double value " + defaultValue.ToString() + " is invalid, expected is json number.");
+                    }
+
+                case Schema.Type.Bytes:
+                    if (defaultValue.Type != JTokenType.String)
+                    {
+                        throw new AvroException("Default bytes value " + defaultValue.ToString() + " is invalid, expected is json string.");
+                    }
+
+                    var en = System.Text.Encoding.GetEncoding("iso-8859-1");
+                    return en.GetBytes((string)defaultValue);
+
+                case Schema.Type.Fixed:
+                    if (defaultValue.Type != JTokenType.String)
+                    {
+                        throw new AvroException("Default fixed value " + defaultValue.ToString() + " is invalid, expected is json string.");
+                    }
+
+                    en = System.Text.Encoding.GetEncoding("iso-8859-1");
+                    int len = (s as FixedSchema).Size;
+                    byte[] bb = en.GetBytes((string)defaultValue);
+                    if (bb.Length != len)
+                    {
+                        throw new AvroException("Default fixed value " + defaultValue.ToString() + " is not of expected length " + len);
+                    }
+
+                    return typeof(byte[]);
+
+                case Schema.Type.String:
+                    if (defaultValue.Type != JTokenType.String)
+                    {
+                        throw new AvroException("Default string value " + defaultValue.ToString() + " is invalid, expected is json string.");
+                    }
+
+                    return (string)defaultValue;
+
+                case Schema.Type.Enumeration:
+                    if (defaultValue.Type != JTokenType.String)
+                    {
+                        throw new AvroException("Default enum value " + defaultValue.ToString() + " is invalid, expected is json string.");
+                    }
+
+                    return (s as EnumSchema).Ordinal((string)defaultValue);
+
+                case Schema.Type.Null:
+                    if (defaultValue.Type != JTokenType.Null)
+                    {
+                        throw new AvroException("Default null value " + defaultValue.ToString() + " is invalid, expected is json null.");
+                    }
+
+                    return null;
+
+                case Schema.Type.Array:
+                    if (defaultValue.Type != JTokenType.Array)
+                    {
+                        throw new AvroException("Default array value " + defaultValue.ToString() + " is invalid, expected is json array.");
+                    }
+
+                    JArray jarr = defaultValue as JArray;
+                    var array = (IEnumerable)Activator.CreateInstance(GetTypeFromSchema(s, false));
+                    var arrayHelper = _classCache.GetArrayHelper(s as ArraySchema, array);
+                    foreach (JToken jitem in jarr)
+                    {
+                        arrayHelper.Add(GetDefaultValue((s as ArraySchema).ItemSchema, jitem));
+                    }
+
+                    return array;
+
+                case Schema.Type.Record:
+                case Schema.Type.Error:
+                    if (defaultValue.Type != JTokenType.Object)
+                    {
+                        throw new AvroException($"Default record value {defaultValue.ToString()} is invalid, expected is json object.");
+                    }
+
+                    RecordSchema rcs = s as RecordSchema;
+                    JObject jo = defaultValue as JObject;
+                    var rec = RecordFactory(GetTypeFromSchema(rcs, false));
+                    if (rec == null)
+                    {
+                        throw new Exception($"Couldn't create type matching schema name {rcs.Fullname}");
+                    }
+
+                    foreach (Field field in rcs)
+                    {
+                        JToken val = jo[field.Name];
+                        if (val == null)
+                            val = field.DefaultValue;
+                        if (val == null)
+                        {
+                            throw new AvroException($"No default value for field {field.Name}");
+                        }
+
+                        _classCache.GetClass(rcs).SetValue(rec, field, GetDefaultValue(field.Schema, val));
+                    }
+
+                    return rec;
+
+                case Schema.Type.Map:
+                    if (defaultValue.Type != JTokenType.Object)
+                    {
+                        throw new AvroException($"Default map value {defaultValue.ToString()} is invalid, expected is json object.");
+                    }
+
+                    jo = defaultValue as JObject;
+                    var map = (System.Collections.IDictionary)Activator.CreateInstance(GetTypeFromSchema(s, false));
+
+                    foreach (KeyValuePair<string, JToken> jp in jo)
+                    {
+                        map.Add(jp.Key, GetDefaultValue((s as MapSchema).ValueSchema, jp.Value));
+                    }
+
+                    return map;
+
+                case Schema.Type.Union:
+                    return GetDefaultValue((s as UnionSchema).Schemas[0], defaultValue);
+
+                default:
+                    throw new AvroException($"Unsupported schema type {s.Tag}");
+            }
+        }
+
+        /// <summary>
+        /// Deserializes a enum. Uses CreateEnum to construct the new enum object.
+        /// </summary>
+        /// <param name="reuse">If appropirate, uses this instead of creating a new enum object.</param>
+        /// <param name="writerSchema">The schema the writer used while writing the enum</param>
+        /// <param name="readerSchema">The schema the reader is using</param>
+        /// <param name="d">The decoder for deserialization.</param>
+        /// <returns>An enum object.</returns>
+        protected override object ReadEnum(object reuse, EnumSchema writerSchema, Schema readerSchema, Decoder d)
+        {
+            var i = d.ReadEnum();
+            var symbol = writerSchema[i];
+            var es = readerSchema as EnumSchema;
+            var enumType = EnumCache.GetEnumeration(es);
+            return Enum.Parse(enumType, symbol);
+        }
+
+        /// <summary>
+        /// Deserializes a record from the stream.
+        /// </summary>
+        /// <param name="reuse">If not null, a record object that could be reused for returning the result</param>
+        /// <param name="writerSchema">The writer's RecordSchema</param>
+        /// <param name="readerSchema">The reader's schema, must be RecordSchema too.</param>
+        /// <param name="dec">The decoder for deserialization</param>
+        /// <returns>The record object just read</returns>
+        protected override object ReadRecord(object reuse, RecordSchema writerSchema, Schema readerSchema, Decoder dec)
+        {
+            RecordSchema rs = (RecordSchema)readerSchema;
+
+            object rec = reuse;
+            if (rec == null)
+            {
+                rec = RecordFactory(GetTypeFromSchema(rs, false));
+                if (rec == null)
+                {
+                    throw new Exception($"Couldn't create type matching schema name {rs.Fullname}");
+                }
+            }
+
+            object obj = null;
+            foreach (Field wf in writerSchema)
+            {
+                try
+                {
+                    Field rf;
+                    if (rs.TryGetField(wf.Name, out rf))
+                    {
+//                        obj = _classCache.GetClass(writerSchema).GetValue(rec, rf);
+                        _classCache.GetClass(writerSchema).SetValue(rec, rf, Read(obj, wf.Schema, rf.Schema, dec));
+                    }
+                    else
+                    {
+                        Skip(wf.Schema, dec);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    throw new AvroException(ex.Message + " in field " + wf.Name, ex);
+                }
+            }
+
+            foreach (Field rf in rs)
+            {
+                if (writerSchema.Contains(rf.Name))
+                {
+                    continue;
+                }
+
+                _classCache.GetClass(rs).SetValue(rec, rf, GetDefaultValue(rf.Schema, rf.DefaultValue));
+            }
+
+            return rec;
+        }
+
+        /// <summary>
+        /// Deserializes a fixed object and returns the object. The default implementation uses CreateFixed()
+        /// and GetFixedBuffer() and returns what CreateFixed() returned.
+        /// </summary>
+        /// <param name="reuse">If appropriate, uses this object instead of creating a new one.</param>
+        /// <param name="writerSchema">The FixedSchema the writer used during serialization.</param>
+        /// <param name="readerSchema">The schema that the readr uses. Must be a FixedSchema with the same
+        /// size as the writerSchema.</param>
+        /// <param name="d">The decoder for deserialization.</param>
+        /// <returns>The deserilized object.</returns>
+        protected override object ReadFixed(object reuse, FixedSchema writerSchema, Schema readerSchema, Decoder d)
+        {
+            FixedSchema rs = readerSchema as FixedSchema;
+            if (rs.Size != writerSchema.Size)
+            {
+                throw new AvroException($"Size mismatch between reader and writer fixed schemas. Writer: {writerSchema}, reader: {readerSchema}");
+            }
+
+            byte[] fixedrec = new byte[rs.Size];
+            d.ReadFixed(fixedrec);
+            return fixedrec;
+        }
+
+        /// <summary>
+        /// Reads an array from the given decoder
+        /// </summary>
+        /// <param name="reuse">object to store data read</param>
+        /// <param name="writerSchema">schema of the object that wrote the data</param>
+        /// <param name="readerSchema">schema of the object that will store the data</param>
+        /// <param name="dec">decoder object that contains the data to be read</param>
+        /// <returns>array</returns>
+        protected override object ReadArray(object reuse, ArraySchema writerSchema, Schema readerSchema, Decoder dec)
+        {
+            ArraySchema rs = readerSchema as ArraySchema;
+            IEnumerable array;
+            ArrayHelper arrayHelper;
+            if (reuse != null)
+            {
+                array = reuse as IEnumerable;
+                if (array == null)
+                    throw new AvroException("array object is not an IEnumerable");
+                arrayHelper = _classCache.GetArrayHelper(rs, array);
+
+                arrayHelper.Clear();
+            }
+            else
+            {
+                array = Activator.CreateInstance(GetTypeFromSchema(rs, false)) as IEnumerable;
+                arrayHelper = _classCache.GetArrayHelper(rs, array);
+            }
+
+            int i = 0;
+            for (int n = (int)dec.ReadArrayStart(); n != 0; n = (int)dec.ReadArrayNext())
+            {
+                for (int j = 0; j < n; j++, i++)
+                {
+                    arrayHelper.Add(Read(null, writerSchema.ItemSchema, rs.ItemSchema, dec));
+                }
+            }
+
+            return array;
+        }
+
+        /// <summary>
+        /// Deserialized an avro map.
+        /// </summary>
+        /// <param name="reuse">If appropriate, use this instead of creating a new map object.</param>
+        /// <param name="writerSchema">The schema the writer used to write the map.</param>
+        /// <param name="readerSchema">The schema the reader is using.</param>
+        /// <param name="d">The decoder for serialization.</param>
+        /// <returns>The deserialized map object.</returns>
+        protected override object ReadMap(object reuse, MapSchema writerSchema, Schema readerSchema, Decoder d)
+        {
+            MapSchema rs = readerSchema as MapSchema;
+            System.Collections.IDictionary map;
+            if (reuse != null)
+            {
+                map = reuse as System.Collections.IDictionary;
+                if (map == null)
+                    throw new AvroException("map object does not implement IDictionary");
+
+                map.Clear();
+            }
+            else
+            {
+                map = (System.Collections.IDictionary)Activator.CreateInstance(GetTypeFromSchema(rs, false));
+            }
+
+            for (int n = (int)d.ReadMapStart(); n != 0; n = (int)d.ReadMapNext())
+            {
+                for (int j = 0; j < n; j++)
+                {
+                    string k = d.ReadString();
+                    map[k] = Read(null, writerSchema.ValueSchema, rs.ValueSchema, d);   // always create new map item
+                }
+            }
+
+            return map;
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/ReflectDefaultWriter.cs b/lang/csharp/src/apache/main/Reflect/ReflectDefaultWriter.cs
new file mode 100644
index 0000000..02d2748
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/ReflectDefaultWriter.cs
@@ -0,0 +1,208 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections;
+using Avro.IO;
+using Avro.Specific;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Class for writing data from any specific objects
+    /// </summary>
+    public class ReflectDefaultWriter : SpecificDefaultWriter
+    {
+        private ClassCache _classCache = new ClassCache();
+
+        /// <summary>
+        /// Class cache
+        /// </summary>
+        /// <value></value>
+        public ClassCache ClassCache { get => _classCache; }
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="objType"></param>
+        /// <param name="schema"></param>
+        /// <param name="cache"></param>
+        public ReflectDefaultWriter(Type objType, Schema schema, ClassCache cache)
+            : base(schema)
+        {
+            if (cache != null)
+            {
+                _classCache = cache;
+            }
+
+            _classCache.LoadClassCache(objType, schema);
+        }
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="schema"></param>
+        public ReflectDefaultWriter(Schema schema)
+            : base(schema)
+        {
+        }
+
+        /// <summary>
+        /// Serialized a record using the given RecordSchema. It uses GetField method
+        /// to extract the field value from the given object.
+        /// </summary>
+        /// <param name="schema">The RecordSchema to use for serialization</param>
+        /// <param name="value">The value to be serialized</param>
+        /// <param name="encoder">The Encoder for serialization</param>
+
+        protected override void WriteRecord(RecordSchema schema, object value, Encoder encoder)
+        {
+            foreach (Field field in schema)
+            {
+                try
+                {
+                    var v = _classCache.GetClass(schema).GetValue(value, field);
+
+                    Write(field.Schema, v, encoder);
+                }
+                catch (Exception ex)
+                {
+                    throw new AvroException(ex.Message + " in field " + field.Name, ex);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Validates that the record is a fixed record object and that the schema in the object is the
+        /// same as the given writer schema. Writes the given fixed record into the given encoder
+        /// </summary>
+        /// <param name="schema">writer schema</param>
+        /// <param name="value">fixed object to write</param>
+        /// <param name="encoder">encoder to write to</param>
+        protected override void WriteFixed(FixedSchema schema, object value, Encoder encoder)
+        {
+            var fixedrec = value as byte[];
+            if (fixedrec == null)
+            {
+                throw new AvroTypeException("Fixed object is not derived from byte[]");
+            }
+
+            if (fixedrec.Length != schema.Size)
+            {
+                throw new AvroTypeException($"Fixed object length is not the same as schema length {schema.Size}");
+            }
+
+            encoder.WriteFixed(fixedrec);
+        }
+
+        /// <summary>
+        /// Serialized an array. The default implementation calls EnsureArrayObject() to ascertain that the
+        /// given value is an array. It then calls GetArrayLength() and GetArrayElement()
+        /// to access the members of the array and then serialize them.
+        /// </summary>
+        /// <param name="schema">The ArraySchema for serialization</param>
+        /// <param name="value">The value being serialized</param>
+        /// <param name="encoder">The encoder for serialization</param>
+        protected override void WriteArray(ArraySchema schema, object value, Encoder encoder)
+        {
+            var arr = value as IEnumerable;
+            if (arr == null)
+            {
+                throw new AvroTypeException("Array does not implement have registered ReflectArray derived type");
+            }
+
+            var arrayHelper = _classCache.GetArrayHelper(schema, (IEnumerable)value);
+            long l = arrayHelper.Count();
+            encoder.WriteArrayStart();
+            encoder.SetItemCount(l);
+            foreach (var v in arr)
+            {
+                encoder.StartItem();
+                Write(schema.ItemSchema, v, encoder);
+            }
+
+            encoder.WriteArrayEnd();
+        }
+
+        /// <summary>
+        /// Writes the given map into the given encoder.
+        /// </summary>
+        /// <param name="schema">writer schema</param>
+        /// <param name="value">map to write</param>
+        /// <param name="encoder">encoder to write to</param>
+        protected override void WriteMap(MapSchema schema, object value, Encoder encoder)
+        {
+            if (value == null)
+            {
+                throw new AvroTypeException("Map is null - use a union for nullable types");
+            }
+
+            base.WriteMap(schema, value, encoder);
+        }
+
+        /// <summary>
+        /// Determines whether an object matches a schema. In the case of enums and records the code looks up the
+        /// Enum and class caches respectively. Used when writing unions.
+        /// </summary>
+        /// <param name="sc"></param>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        protected override bool Matches(Schema sc, object obj)
+        {
+            if (obj == null && sc.Tag != Schema.Type.Null)
+            {
+                return false;
+            }
+
+            switch (sc.Tag)
+            {
+                case Schema.Type.Null:
+                    return obj == null;
+                case Schema.Type.Boolean:
+                    return obj is bool;
+                case Schema.Type.Int:
+                    return obj is int;
+                case Schema.Type.Long:
+                    return obj is long;
+                case Schema.Type.Float:
+                    return obj is float;
+                case Schema.Type.Double:
+                    return obj is double;
+                case Schema.Type.Bytes:
+                    return obj is byte[];
+                case Schema.Type.String:
+                    return obj is string;
+                case Schema.Type.Error:
+                case Schema.Type.Record:
+                    return _classCache.GetClass(sc as RecordSchema).GetClassType() == obj.GetType();
+                case Schema.Type.Enumeration:
+                    return EnumCache.GetEnumeration(sc as EnumSchema) == obj.GetType();
+                case Schema.Type.Array:
+                    return obj is IEnumerable;
+                case Schema.Type.Map:
+                    return obj is IDictionary;
+                case Schema.Type.Union:
+                    return false;   // Union directly within another union not allowed!
+                case Schema.Type.Fixed:
+                    return obj is byte[];
+                default:
+                    throw new AvroException("Unknown schema type: " + sc.Tag);
+            }
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/ReflectReader.cs b/lang/csharp/src/apache/main/Reflect/ReflectReader.cs
new file mode 100644
index 0000000..5a08ac4
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/ReflectReader.cs
@@ -0,0 +1,93 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using Avro.IO;
+using Avro.Generic;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Reader wrapper class for reading data and storing into specific classes
+    /// </summary>
+    /// <typeparam name="T">Specific class type</typeparam>
+    public class ReflectReader<T> : DatumReader<T>
+    {
+        /// <summary>
+        /// Reader class for reading data and storing into specific classes
+        /// </summary>
+        private readonly ReflectDefaultReader _reader;
+
+        /// <summary>
+        /// Default reader
+        /// </summary>
+        /// <value></value>
+        public ReflectDefaultReader Reader { get => _reader; }
+
+        /// <summary>
+        /// Schema for the writer class
+        /// </summary>
+        public Schema WriterSchema { get => _reader.WriterSchema; }
+
+        /// <summary>
+        /// Schema for the reader class
+        /// </summary>
+        public Schema ReaderSchema { get => _reader.ReaderSchema; }
+
+        /// <summary>
+        /// Constructs a generic reader for the given schemas using the DefaultReader. If the
+        /// reader's and writer's schemas are different this class performs the resolution.
+        /// </summary>
+        /// <param name="writerSchema">The schema used while generating the data</param>
+        /// <param name="readerSchema">The schema desired by the reader</param>
+        /// <param name="cache">Class cache</param>
+        public ReflectReader(Schema writerSchema, Schema readerSchema, ClassCache cache = null)
+        {
+            _reader = new ReflectDefaultReader(typeof(T), writerSchema, readerSchema, cache);
+        }
+
+        /// <summary>
+        /// Constructs a generic reader from an instance of a ReflectDefaultReader (non-generic)
+        /// </summary>
+        /// <param name="reader"></param>
+        public ReflectReader(ReflectDefaultReader reader)
+        {
+            _reader = reader;
+        }
+
+        /// <summary>
+        /// Generic read function
+        /// </summary>
+        /// <param name="reuse">object to store data read</param>
+        /// <param name="dec">decorder to use for reading data</param>
+        /// <returns></returns>
+        public T Read(T reuse, Decoder dec)
+        {
+            return _reader.Read(reuse, dec);
+        }
+
+        /// <summary>
+        /// Generic read function
+        /// </summary>
+        /// <param name="dec">decorder to use for reading data</param>
+        /// <returns></returns>
+        public T Read(Decoder dec)
+        {
+            return _reader.Read(default(T), dec);
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/ReflectWriter.cs b/lang/csharp/src/apache/main/Reflect/ReflectWriter.cs
new file mode 100644
index 0000000..829c204
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/ReflectWriter.cs
@@ -0,0 +1,73 @@
+/* 
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using Avro.IO;
+using Avro.Generic;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Generic wrapper class for writing data from specific objects
+    /// </summary>
+    /// <typeparam name="T">type name of specific object</typeparam>
+    public class ReflectWriter<T> : DatumWriter<T>
+    {
+        /// <summary>
+        /// Default writer
+        /// </summary>
+        /// <value></value>
+        public ReflectDefaultWriter Writer { get => _writer; }
+
+        private readonly ReflectDefaultWriter _writer;
+
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="schema"></param>
+        /// <param name="cache"></param>
+        public ReflectWriter(Schema schema, ClassCache cache = null)
+            : this(new ReflectDefaultWriter(typeof(T), schema, cache))
+        {
+        }
+
+        /// <summary>
+        /// The schema
+        /// </summary>
+        /// <value></value>
+        public Schema Schema { get => _writer.Schema; }
+
+        /// <summary>
+        /// Constructor with already created default writer.
+        /// </summary>
+        /// <param name="writer"></param>
+        public ReflectWriter(ReflectDefaultWriter writer)
+        {
+            _writer = writer;
+        }
+
+        /// <summary>
+        /// Serializes the given object using this writer's schema.
+        /// </summary>
+        /// <param name="value">The value to be serialized</param>
+        /// <param name="encoder">The encoder to use for serializing</param>
+        public void Write(T value, Encoder encoder)
+        {
+            _writer.Write(value, encoder);
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Reflect/TypedFieldConverter.cs b/lang/csharp/src/apache/main/Reflect/TypedFieldConverter.cs
new file mode 100644
index 0000000..bd0a0ec
--- /dev/null
+++ b/lang/csharp/src/apache/main/Reflect/TypedFieldConverter.cs
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Reflection;
+
+namespace Avro.Reflect
+{
+    /// <summary>
+    /// Constructor
+    /// </summary>
+    /// <typeparam name="A">Avro type</typeparam>
+    /// <typeparam name="P">Property type</typeparam>
+    public abstract class TypedFieldConverter<A, P> : IAvroFieldConverter
+    {
+        /// <summary>
+        /// Convert from Avro type to property type
+        /// </summary>
+        /// <param name="o">Avro value</param>
+        /// <param name="s">Schema</param>
+        /// <returns>Property value</returns>
+        public abstract P From(A o, Schema s);
+
+        /// <summary>
+        /// Convert from property type to Avro type
+        /// </summary>
+        /// <param name="o"></param>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public abstract A To(P o, Schema s);
+
+        /// <summary>
+        /// Implement untyped interface
+        /// </summary>
+        /// <param name="o"></param>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public object FromAvroType(object o, Schema s)
+        {
+            if (!typeof(A).IsAssignableFrom(o.GetType()))
+            {
+                throw new AvroException($"Converter from {typeof(A).Name} to {typeof(P).Name} cannot convert object of type {o.GetType().Name} to {typeof(A).Name}, object {o.ToString()}");
+            }
+
+            return From((A)o, s);
+        }
+
+        /// <summary>
+        /// Implement untyped interface
+        /// </summary>
+        /// <returns></returns>
+        public Type GetAvroType()
+        {
+            return typeof(A);
+        }
+
+        /// <summary>
+        /// Implement untyped interface
+        /// </summary>
+        /// <returns></returns>
+        public Type GetPropertyType()
+        {
+            return typeof(P);
+        }
+
+        /// <summary>
+        /// Implement untyped interface
+        /// </summary>
+        /// <param name="o"></param>
+        /// <param name="s"></param>
+        /// <returns></returns>
+        public object ToAvroType(object o, Schema s)
+        {
+            if (!typeof(P).IsAssignableFrom(o.GetType()))
+            {
+                throw new AvroException($"Converter from {typeof(A).Name} to {typeof(P).Name} cannot convert object of type {o.GetType().Name} to {typeof(P).Name}, object {o.ToString()}");
+            }
+
+            return To((P)o, s);
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/test/Reflect/CompareUtils.cs b/lang/csharp/src/apache/test/Reflect/CompareUtils.cs
new file mode 100644
index 0000000..e7584eb
--- /dev/null
+++ b/lang/csharp/src/apache/test/Reflect/CompareUtils.cs
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+namespace Avro.Test
+{
+    public static class ExtensionMethods
+    {
+        public static bool SequenceEqual(this byte[] source, byte[] target)
+        {
+            if (source.Length != target.Length)
+            {
+                return false;
+            }
+            for (int i = 0; i < source.Length; i++)
+            {
+                if (source[i] != target[i])
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public static void ForEach<T1,T2>( this IEnumerable<T1> e1, IEnumerable<T2> e2, Action<T1,T2> action)
+        {
+            foreach(var items in e1.Zip(e2, Tuple.Create))
+            {
+                action(items.Item1, items.Item2);
+            }
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/test/Reflect/TestArray.cs b/lang/csharp/src/apache/test/Reflect/TestArray.cs
new file mode 100644
index 0000000..ede5af3
--- /dev/null
+++ b/lang/csharp/src/apache/test/Reflect/TestArray.cs
@@ -0,0 +1,247 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.IO;
+using Avro.IO;
+using Avro.Reflect;
+using NUnit.Framework;
+using System.Collections;
+
+namespace Avro.Test
+{
+
+
+    [TestFixture]
+    public class TestArray
+    {
+        private class ListRec
+        {
+            public string S { get; set; }
+        }
+
+        private const string _simpleList = @"
+        {
+            ""namespace"": ""MessageTypes"",
+            ""type"": ""array"",
+            ""doc"": ""A simple list with a string."",
+            ""name"": ""A"",
+            ""items"": ""string""
+        }";
+
+        private const string _recordList = @"
+        {
+            ""namespace"": ""MessageTypes"",
+            ""type"": ""array"",
+            ""helper"": ""arrayOfA"",
+            ""items"": {
+                ""type"": ""record"",
+                ""doc"": ""A simple type with a fixed."",
+                ""name"": ""A"",
+                ""fields"": [
+                    { ""name"" : ""S"", ""type"" : ""string"" }
+                ]
+            }
+        }";
+
+
+
+        [TestCase]
+        public void ListTest()
+        {
+            var schema = Schema.Parse(_simpleList);
+            var fixedRecWrite = new List<string>() {"value"};
+
+            var writer = new ReflectWriter<List<string>>(schema);
+            var reader = new ReflectReader<List<string>>(schema, schema);
+
+            using (var stream = new MemoryStream(256))
+            {
+                writer.Write(fixedRecWrite, new BinaryEncoder(stream));
+                stream.Seek(0, SeekOrigin.Begin);
+                var fixedRecRead = reader.Read(new BinaryDecoder(stream));
+                Assert.IsTrue(fixedRecRead.Count == 1);
+                Assert.AreEqual(fixedRecWrite[0],fixedRecRead[0]);
+            }
+        }
+
+        [TestCase]
+        public void ListRecTest()
+        {
+            var schema = Schema.Parse(_recordList);
+            var fixedRecWrite = new List<ListRec>() { new ListRec() { S = "hello"}};
+
+            var writer = new ReflectWriter<List<ListRec>>(schema);
+            var reader = new ReflectReader<List<ListRec>>(schema, schema);
+
+            using (var stream = new MemoryStream(256))
+            {
+                writer.Write(fixedRecWrite, new BinaryEncoder(stream));
+                stream.Seek(0, SeekOrigin.Begin);
+                var fixedRecRead = reader.Read(new BinaryDecoder(stream));
+                Assert.IsTrue(fixedRecRead.Count == 1);
+                Assert.AreEqual(fixedRecWrite[0].S,fixedRecRead[0].S);
+            }
+        }
+
+        public class ConcurrentQueueHelper<T> : ArrayHelper
+        {
+
+            /// <summary>
+            /// Return the number of elements in the array.
+            /// </summary>
+            /// <value></value>
+            public override int Count()
+            {
+                ConcurrentQueue<T> e = (ConcurrentQueue<T>)Enumerable;
+                return e.Count;
+            }
+            /// <summary>
+            /// Add an element to the array.
+            /// </summary>
+            /// <value></value>
+            public override void Add(object o)
+            {
+                ConcurrentQueue<T> e = (ConcurrentQueue<T>)Enumerable;
+                e.Enqueue((T)o);
+            }
+            /// <summary>
+            /// Clear the array.
+            /// </summary>
+            /// <value></value>
+            public override void Clear()
+            {
+                ConcurrentQueue<T> e = (ConcurrentQueue<T>)Enumerable;
+#if NET461
+                while (e.TryDequeue(out _)) { }
+#else
+                e.Clear();
+#endif
+            }
+
+            /// <summary>
+            /// Type of the array to create when deserializing
+            /// </summary>
+            /// <value></value>
+            public override Type ArrayType
+            {
+                get => typeof(ConcurrentQueue<>);
+            }
+
+            /// <summary>
+            /// Constructor
+            /// </summary>
+            public ConcurrentQueueHelper(IEnumerable enumerable) : base(enumerable)
+            {
+                Enumerable = enumerable;
+            }
+        }
+
+        private class ConcurrentQueueRec
+        {
+            public string S { get; set; }
+        }
+
+
+        [TestCase]
+        public void ConcurrentQueueTest()
+        {
+            var schema = Schema.Parse(_recordList);
+            var fixedRecWrite = new ConcurrentQueue<ConcurrentQueueRec>();
+            fixedRecWrite.Enqueue(new ConcurrentQueueRec() { S = "hello"});
+            var cache = new ClassCache();
+            cache.AddArrayHelper("arrayOfA", typeof(ConcurrentQueueHelper<ConcurrentQueueRec>));
+            var writer = new ReflectWriter<ConcurrentQueue<ConcurrentQueueRec>>(schema, cache);
+            var reader = new ReflectReader<ConcurrentQueue<ConcurrentQueueRec>>(schema, schema, cache);
+
+            using (var stream = new MemoryStream(256))
+            {
+                writer.Write(fixedRecWrite, new BinaryEncoder(stream));
+                stream.Seek(0, SeekOrigin.Begin);
+                var fixedRecRead = reader.Read(new BinaryDecoder(stream));
+                Assert.IsTrue(fixedRecRead.Count == 1);
+                ConcurrentQueueRec wRec = null;
+                fixedRecWrite.TryDequeue(out wRec);
+                Assert.NotNull(wRec);
+                ConcurrentQueueRec rRec = null;
+                fixedRecRead.TryDequeue(out rRec);
+                Assert.NotNull(rRec);
+                Assert.AreEqual(wRec.S,rRec.S);
+            }
+        }
+
+        private const string _multiList = @"
+        {
+            ""type"": ""record"",
+            ""doc"": ""Multiple arrays."",
+            ""name"": ""A"",
+            ""fields"": [
+                { ""name"" : ""one"", ""type"" :
+                    {
+                        ""type"": ""array"",
+                        ""items"": ""string""
+                    }
+                },
+                { ""name"" : ""two"", ""type"" :
+                    {
+                        ""type"": ""array"",
+                        ""helper"": ""twoArray"",
+                        ""items"": ""string""
+                    }
+                }
+            ]
+        }";
+
+        private class MultiList
+        {
+            public List<string> one {get;set;}
+            public ConcurrentQueue<string> two {get;set;}
+        }
+        [TestCase]
+        public void MultiQueueTest()
+        {
+            var schema = Schema.Parse(_multiList);
+            var fixedRecWrite = new MultiList() { one = new List<string>(), two = new ConcurrentQueue<string>() };
+            fixedRecWrite.one.Add("hola");
+            fixedRecWrite.two.Enqueue("hello");
+            var cache = new ClassCache();
+            cache.AddArrayHelper("twoArray", typeof(ConcurrentQueueHelper<string>));
+            var writer = new ReflectWriter<MultiList>(schema, cache);
+            var reader = new ReflectReader<MultiList>(schema, schema, cache);
+
+            using (var stream = new MemoryStream(256))
+            {
+                writer.Write(fixedRecWrite, new BinaryEncoder(stream));
+                stream.Seek(0, SeekOrigin.Begin);
+                var fixedRecRead = reader.Read(new BinaryDecoder(stream));
+                Assert.IsTrue(fixedRecRead.one.Count == 1);
+                Assert.IsTrue(fixedRecRead.two.Count == 1);
+                Assert.AreEqual(fixedRecWrite.one[0], fixedRecRead.one[0]);
+                string wRec = null;
+                fixedRecWrite.two.TryDequeue(out wRec);
+                Assert.NotNull(wRec);
+                string rRec = null;
+                fixedRecRead.two.TryDequeue(out rRec);
+                Assert.NotNull(rRec);
+                Assert.AreEqual(wRec, rRec);
+            }
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/test/Reflect/TestFixed.cs b/lang/csharp/src/apache/test/Reflect/TestFixed.cs
new file mode 100644
index 0000000..35e8334
--- /dev/null
+++ b/lang/csharp/src/apache/test/Reflect/TestFixed.cs
@@ -0,0 +1,162 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.IO;
+using Avro.IO;
+using Avro.Generic;
+using Avro.Reflect;
+using NUnit.Framework;
+
+namespace Avro.Test
+{
+
+    [TestFixture]
+    public class TestFixed
+    {
+            public class ByteArrayFixedRec
+        {
+            public byte[] myFixed { get; set; }
+        }
+
+        public class GenericFixedRec
+        {
+            public GenericFixed myFixed { get; set; }
+        }
+
+        public class GenericFixedConverter : TypedFieldConverter<byte[],GenericFixed>
+        {
+            public override GenericFixed From(byte[] o, Schema s)
+            {
+                return new GenericFixed(s as FixedSchema, o);
+            }
+
+            public override byte[] To(GenericFixed o, Schema s)
+            {
+                return o.Value;
+            }
+        }
+
+        public class GenericFixedConverterRec
+        {
+            [AvroField(typeof(GenericFixedConverter))]
+            public GenericFixed myFixed { get; set; }
+        }
+        private const string _fixedSchema = @"
+        {
+            ""namespace"": ""MessageTypes"",
+            ""type"": ""record"",
+            ""doc"": ""A simple type with a fixed."",
+            ""name"": ""A"",
+            ""fields"": [
+                { ""name"" : ""myFixed"", ""type"" :
+                    {
+                        ""type"": ""fixed"",
+                        ""size"": 16,
+                        ""name"": ""MyFixed""
+                    }
+                }
+            ]
+        }";
+
+        [TestCase]
+        public void ByteArray()
+        {
+            var schema = Schema.Parse(_fixedSchema);
+            var fixedRecWrite = new ByteArrayFixedRec() { myFixed = new byte[16] {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6} };
+            var fixedRecBad = new ByteArrayFixedRec() { myFixed = new byte[10] };
+            ByteArrayFixedRec fixedRecRead = null;
+
+            var writer = new ReflectWriter<ByteArrayFixedRec>(schema);
+            var reader = new ReflectReader<ByteArrayFixedRec>(schema, schema);
+
+            Assert.Throws(typeof(AvroException), ()=> {
+                using (var stream = new MemoryStream(256))
+                {
+                    writer.Write(fixedRecBad, new BinaryEncoder(stream));
+                }
+            });
+            using (var stream = new MemoryStream(256))
+            {
+                writer.Write(fixedRecWrite, new BinaryEncoder(stream));
+                stream.Seek(0, SeekOrigin.Begin);
+                fixedRecRead = reader.Read(null, new BinaryDecoder(stream));
+                Assert.IsTrue(fixedRecRead.myFixed.Length == 16);
+                Assert.IsTrue(fixedRecWrite.myFixed.SequenceEqual(fixedRecRead.myFixed));
+            }
+        }
+
+        [TestCase]
+        public void GenericFixedConverterTest()
+        {
+            var schema = Schema.Parse(_fixedSchema);
+            var rs = schema as RecordSchema;
+            FixedSchema fs = null;
+            foreach (var f in rs.Fields)
+            {
+                if (f.Name == "myFixed")
+                {
+                    fs = f.Schema as FixedSchema;
+                }
+            }
+            var fixedRecWrite = new GenericFixedConverterRec() { myFixed = new GenericFixed(fs) {Value = new byte[16] {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6} }};
+            GenericFixedConverterRec fixedRecRead = null;
+
+            var writer = new ReflectWriter<GenericFixedConverterRec>(schema);
+            var reader = new ReflectReader<GenericFixedConverterRec>(schema, schema);
+
+            using (var stream = new MemoryStream(256))
+            {
+                writer.Write(fixedRecWrite, new BinaryEncoder(stream));
+                stream.Seek(0, SeekOrigin.Begin);
+                fixedRecRead = reader.Read(null, new BinaryDecoder(stream));
+                Assert.IsTrue(fixedRecRead.myFixed.Value.Length == 16);
+                Assert.IsTrue(fixedRecWrite.myFixed.Value.SequenceEqual(fixedRecRead.myFixed.Value));
+            }
+        }
+
+        [TestCase]
+        public void GenericFixedDefaultConverter()
+        {
+            var schema = Schema.Parse(_fixedSchema);
+            var rs = schema as RecordSchema;
+            FixedSchema fs = null;
+            foreach (var f in rs.Fields)
+            {
+                if (f.Name == "myFixed")
+                {
+                    fs = f.Schema as FixedSchema;
+                }
+            }
+            var fixedRecWrite = new GenericFixedRec() { myFixed = new GenericFixed(fs) {Value = new byte[16] {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6} }};
+            GenericFixedRec fixedRecRead = null;
+
+            ClassCache.AddDefaultConverter<byte[], GenericFixed>((a,s)=>new GenericFixed(s as FixedSchema, a), (p,s)=>p.Value);
+            var writer = new ReflectWriter<GenericFixedRec>(schema);
+            var reader = new ReflectReader<GenericFixedRec>(schema, schema);
+
+            using (var stream = new MemoryStream(256))
+            {
+                writer.Write(fixedRecWrite, new BinaryEncoder(stream));
+                stream.Seek(0, SeekOrigin.Begin);
+                fixedRecRead = reader.Read(null, new BinaryDecoder(stream));
+                Assert.IsTrue(fixedRecRead.myFixed.Value.Length == 16);
+                Assert.IsTrue(fixedRecWrite.myFixed.Value.SequenceEqual(fixedRecRead.myFixed.Value));
+            }
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/test/Reflect/TestFromAvroProject.cs b/lang/csharp/src/apache/test/Reflect/TestFromAvroProject.cs
new file mode 100644
index 0000000..dff2a69
--- /dev/null
+++ b/lang/csharp/src/apache/test/Reflect/TestFromAvroProject.cs
@@ -0,0 +1,369 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ using System;
+using System.IO;
+using System.Collections.Generic;
+using Avro;
+using Avro.IO;
+using Avro.Generic;
+using Avro.Specific;
+using Avro.Reflect;
+using NUnit.Framework;
+
+namespace Avro.Test
+{
+    public enum MyEnum
+    {
+        A,
+        B,
+        C
+    }
+    public class A
+    {
+        public long f1 { get; set; }
+    }
+    public class newRec
+    {
+        public long f1 { get; set; }
+    }
+
+    public class Z
+    {
+        public int? myUInt { get; set; }
+
+        public long? myULong { get; set; }
+
+        public bool? myUBool { get; set; }
+
+        public double? myUDouble { get; set; }
+
+        public float? myUFloat { get; set; }
+
+        public byte[] myUBytes { get; set; }
+
+        public string myUString { get; set; }
+
+        public int myInt { get; set; }
+
+        public long myLong { get; set; }
+
+        public bool myBool { get; set; }
+
+        public double myDouble { get; set; }
+
+        public float myFloat { get; set; }
+
+        public byte[] myBytes { get; set; }
+
+        public string myString { get; set; }
+
+        public object myNull { get; set; }
+
+        public byte[] myFixed { get; set; }
+
+        public A myA { get; set; }
+
+        public MyEnum myE { get; set; }
+
+        public List<byte[]> myArray { get; set; }
+
+        public List<newRec> myArray2 { get; set; }
+
+        public Dictionary<string, string> myMap { get; set; }
+
+        public Dictionary<string, newRec> myMap2 { get; set; }
+
+        public object myObject { get; set; }
+
+        public List<List<object>> myArray3 { get; set; }
+    }
+    [TestFixture]
+    public class TestFromAvroProject
+    {
+        private const string _avroTestSchemaV1 = @"{
+        ""protocol"" : ""MyProtocol"",
+        ""namespace"" : ""com.foo"",
+        ""types"" :
+        [
+            {
+                ""type"" : ""record"",
+                ""name"" : ""A"",
+                ""fields"" : [ { ""name"" : ""f1"", ""type"" : ""long"" } ]
+            },
+            {
+                ""type"" : ""enum"",
+                ""name"" : ""MyEnum"",
+                ""symbols"" : [ ""A"", ""B"", ""C"" ]
+            },
+            {
+                ""type"": ""fixed"",
+                ""size"": 16,
+                ""name"": ""MyFixed""
+            },
+            {
+                ""type"" : ""record"",
+                ""name"" : ""Z"",
+                ""fields"" :
+                [
+                    { ""name"" : ""myUInt"", ""type"" : [ ""int"", ""null"" ] },
+                    { ""name"" : ""myULong"", ""type"" : [ ""long"", ""null"" ] },
+                    { ""name"" : ""myUBool"", ""type"" : [ ""boolean"", ""null"" ] },
+                    { ""name"" : ""myUDouble"", ""type"" : [ ""double"", ""null"" ] },
+                    { ""name"" : ""myUFloat"", ""type"" : [ ""float"", ""null"" ] },
+                    { ""name"" : ""myUBytes"", ""type"" : [ ""bytes"", ""null"" ] },
+                    { ""name"" : ""myUString"", ""type"" : [ ""string"", ""null"" ] },
+                    { ""name"" : ""myInt"", ""type"" : ""int"" },
+                    { ""name"" : ""myLong"", ""type"" : ""long"" },
+                    { ""name"" : ""myBool"", ""type"" : ""boolean"" },
+                    { ""name"" : ""myDouble"", ""type"" : ""double"" },
+                    { ""name"" : ""myFloat"", ""type"" : ""float"" },
+                    { ""name"" : ""myBytes"", ""type"" : ""bytes"" },
+                    { ""name"" : ""myString"", ""type"" : ""string"" },
+                    { ""name"" : ""myNull"", ""type"" : ""null"" },
+                    { ""name"" : ""myFixed"", ""type"" : ""MyFixed"" },
+                    { ""name"" : ""myA"", ""type"" : ""A"" },
+                    { ""name"" : ""myE"", ""type"" : ""MyEnum"" },
+                    { ""name"" : ""myArray"", ""type"" : { ""type"" : ""array"", ""items"" : ""bytes"" } },
+                    { ""name"" : ""myArray2"", ""type"" : { ""type"" : ""array"", ""items"" : { ""type"" : ""record"", ""name"" : ""newRec"", ""fields"" : [ { ""name"" : ""f1"", ""type"" : ""long""} ] } } },
+                    { ""name"" : ""myMap"", ""type"" : { ""type"" : ""map"", ""values"" : ""string"" } },
+                    { ""name"" : ""myMap2"", ""type"" : { ""type"" : ""map"", ""values"" : ""newRec"" } },
+                    { ""name"" : ""myObject"", ""type"" : [ ""MyEnum"", ""A"", ""null"" ] },
+                    { ""name"" : ""myArray3"", ""type"" : { ""type"" : ""array"", ""items"" : { ""type"" : ""array"", ""items"" : [ ""double"", ""string"", ""null"" ] } } }
+                ]
+            }
+        ]
+        }";
+
+        public Z SerializeDeserialize(Z z)
+        {
+            try
+            {
+                Protocol protocol = Protocol.Parse(_avroTestSchemaV1);
+                Schema schema = null;
+                foreach (var s in protocol.Types)
+                {
+                    if (s.Name == "Z")
+                    {
+                        schema = s;
+                    }
+                }
+
+                var avroWriter = new ReflectWriter<Z>(schema);
+                var avroReader = new ReflectReader<Z>(schema, schema);
+
+                byte[] serialized;
+
+                using (var stream = new MemoryStream(256))
+                {
+                    avroWriter.Write(z, new BinaryEncoder(stream));
+                    serialized = stream.ToArray();
+                }
+
+                Z deserialized = null;
+                using (var stream = new MemoryStream(serialized))
+                {
+                    deserialized = avroReader.Read(default(Z), new BinaryDecoder(stream));
+                }
+
+                return deserialized;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.ToString());
+                throw ex;
+            }
+        }
+
+        [TestCase]
+        public void DefaultZ()
+        {
+            var z = new Z()
+            {
+                myBytes = new byte[10],
+                myString = "123",
+                myFixed = new byte[16],
+                myA = new A(),
+                myArray = new List<byte[]>(),
+                myArray2 = new List<newRec>(),
+                myMap = new Dictionary<string, string>(),
+                myMap2 = new Dictionary<string, newRec>(),
+                myArray3 = new List<List<object>>()
+            };
+
+            var zz = SerializeDeserialize(z);
+            DoAssertions(z, zz);
+
+        }
+
+        private void DoAssertions(Z z, Z zz)
+        {
+                        Assert.IsNotNull(zz);
+            Assert.AreEqual(z.myUInt, zz.myUInt);
+            Assert.AreEqual(z.myULong, zz.myULong);
+            Assert.AreEqual(z.myUBool, zz.myUBool);
+            Assert.AreEqual(z.myUDouble, zz.myUDouble);
+            Assert.AreEqual(z.myUFloat, zz.myUFloat);
+            if (z.myUBytes == null)
+            {
+                Assert.IsNull(zz.myUBytes);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myUBytes);
+                Assert.IsTrue(z.myUBytes.SequenceEqual(zz.myUBytes));
+            }
+            Assert.AreEqual(z.myUString, zz.myUString);
+            Assert.AreEqual(z.myInt, zz.myInt);
+            Assert.AreEqual(z.myLong, zz.myLong);
+            Assert.AreEqual(z.myBool, zz.myBool);
+            Assert.AreEqual(z.myDouble, zz.myDouble);
+            Assert.AreEqual(z.myFloat, zz.myFloat);
+            if (z.myBytes == null)
+            {
+                Assert.IsNull(zz.myBytes);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myBytes);
+                Assert.IsTrue(z.myBytes.SequenceEqual(zz.myBytes));
+            }
+            Assert.AreEqual(z.myString, zz.myString);
+            Assert.AreEqual(z.myNull, zz.myNull);
+            if (z.myFixed == null)
+            {
+                Assert.IsNull(zz.myFixed);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myFixed);
+                Assert.AreEqual(z.myFixed.Length, zz.myFixed.Length);
+                Assert.IsTrue(z.myFixed.SequenceEqual(zz.myFixed));
+            }
+            if (z.myA == null)
+            {
+                Assert.IsNull(zz.myA);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myA);
+                Assert.AreEqual(z.myA.f1, zz.myA.f1);
+            }
+            Assert.AreEqual(z.myE, zz.myE);
+            if (z.myArray == null)
+            {
+                Assert.IsNull(zz.myArray);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myArray);
+                Assert.AreEqual(z.myArray.Count, zz.myArray.Count);
+                z.myArray.ForEach(zz.myArray, (i1,i2)=>Assert.IsTrue(i1.SequenceEqual(i2)));
+            }
+            if (z.myArray2 == null)
+            {
+                Assert.IsNull(zz.myArray2);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myArray2);
+                Assert.AreEqual(z.myArray2.Count, zz.myArray2.Count);
+                z.myArray2.ForEach(zz.myArray2, (i1,i2)=>Assert.AreEqual(i1.f1, i2.f1));
+            }
+            if (z.myArray3 == null)
+            {
+                Assert.IsNull(zz.myArray3);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myArray3);
+                Assert.AreEqual(z.myArray3.Count, zz.myArray3.Count);
+                z.myArray3.ForEach(zz.myArray3, (i1,i2)=>i1.ForEach(i2, (j1,j2)=>Assert.AreEqual(j1,j2)));
+            }
+            if (z.myMap == null)
+            {
+                Assert.IsNull(zz.myMap);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myMap);
+                Assert.AreEqual(z.myMap.Count, zz.myMap.Count);
+                z.myMap.ForEach(zz.myMap, (i1,i2)=>{Assert.AreEqual(i1.Key, i2.Key); Assert.AreEqual(i1.Value, i2.Value);});
+            }
+            if (z.myMap2 == null)
+            {
+                Assert.IsNull(zz.myMap2);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myMap2);
+                Assert.AreEqual(z.myMap2.Count, zz.myMap2.Count);
+                z.myMap2.ForEach(zz.myMap2, (i1,i2)=>{Assert.AreEqual(i1.Key, i2.Key); Assert.AreEqual(i1.Value.f1, i2.Value.f1);});
+            }
+             if (z.myObject == null)
+            {
+                Assert.IsNull(zz.myObject);
+            }
+            else
+            {
+                Assert.IsNotNull(zz.myObject);
+                Assert.IsTrue(z.myObject.GetType() == zz.myObject.GetType());
+            }
+        }
+        [TestCase]
+        public void PopulatedZ()
+        {
+            var z = new Z()
+            {
+                myUInt = 1,
+                myULong = 2L,
+                myUBool = true,
+                myUDouble = 3.14,
+                myUFloat = (float)1.59E-3,
+                myUBytes = new byte[3] { 0x01, 0x02, 0x03 },
+                myUString = "abc",
+                myInt = 1,
+                myLong = 2L,
+                myBool = true,
+                myDouble = 3.14,
+                myFloat = (float)1.59E-2,
+                myBytes = new byte[3] { 0x01, 0x02, 0x03 },
+                myString = "def",
+                myNull = null,
+                myFixed = new byte[16] { 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04 },
+                myA = new A() { f1 = 3L },
+                myE = MyEnum.B,
+                myArray = new List<byte[]>() { new byte[] { 0x01, 0x02, 0x03, 0x04 } },
+                myArray2 = new List<newRec>() { new newRec() { f1 = 4L } },
+                myMap = new Dictionary<string, string>()
+                {
+                    ["abc"] = "123"
+                },
+                myMap2 = new Dictionary<string, newRec>()
+                {
+                    ["abc"] = new newRec() { f1 = 5L }
+                },
+                myObject = new A() { f1 = 6L },
+                myArray3 = new List<List<object>>() { new List<object>() { 7.0, "def" } }
+            };
+
+            var zz = SerializeDeserialize(z);
+            DoAssertions(z, zz);
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/test/Reflect/TestLogMessage.cs b/lang/csharp/src/apache/test/Reflect/TestLogMessage.cs
new file mode 100644
index 0000000..8156b35
--- /dev/null
+++ b/lang/csharp/src/apache/test/Reflect/TestLogMessage.cs
@@ -0,0 +1,113 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Avro.IO;
+using Avro.Reflect;
+using NUnit.Framework;
+
+namespace Avro.Test
+{
+    public enum MessageTypes
+    {
+        None,
+        Verbose,
+        Info,
+        Warning,
+        Error
+    }
+
+    public class LogMessage
+    {
+        private Dictionary<string, string> _tags = new Dictionary<string, string>();
+
+        public string IP { get; set; }
+
+        [AvroField("Message")]
+        public string message { get; set; }
+
+        [AvroField(typeof(DateTimeOffsetToLongConverter))]
+        public DateTimeOffset TimeStamp { get; set; }
+
+        public Dictionary<string, string> Tags { get => _tags; set => _tags = value; }
+
+        public MessageTypes Severity { get; set; }
+    }
+
+    [TestFixture]
+    public class TestLogMessage
+    {
+        private const string _logMessageSchemaV1 = @"
+        {
+            ""namespace"": ""MessageTypes"",
+            ""type"": ""record"",
+            ""doc"": ""A simple log message type as used by this blog post."",
+            ""name"": ""LogMessage"",
+            ""fields"": [
+                { ""name"": ""IP"", ""type"": ""string"" },
+                { ""name"": ""Message"", ""type"": ""string"" },
+                { ""name"": ""TimeStamp"", ""type"": ""long"" },
+                { ""name"": ""Tags"",""type"":
+                    { ""type"": ""map"",
+                        ""values"": ""string""},
+                        ""default"": {}},
+                { ""name"": ""Severity"",
+                ""type"": { ""namespace"": ""MessageTypes"",
+                    ""type"": ""enum"",
+                    ""doc"": ""Enumerates the set of allowable log levels."",
+                    ""name"": ""LogLevel"",
+                    ""symbols"": [""None"", ""Verbose"", ""Info"", ""Warning"", ""Error""]}}
+            ]
+        }";
+
+        [TestCase]
+        public void Serialize()
+        {
+            var schema = Schema.Parse(_logMessageSchemaV1);
+            var avroWriter = new ReflectWriter<LogMessage>(schema);
+            var avroReader = new ReflectReader<LogMessage>(schema, schema);
+
+            byte[] serialized;
+
+            var logMessage = new LogMessage()
+            {
+                IP = "10.20.30.40",
+                message = "Log entry",
+                Severity = MessageTypes.Error
+            };
+
+            using (var stream = new MemoryStream(256))
+            {
+                avroWriter.Write(logMessage, new BinaryEncoder(stream));
+                serialized = stream.ToArray();
+            }
+
+            LogMessage deserialized = null;
+            using (var stream = new MemoryStream(serialized))
+            {
+                deserialized = avroReader.Read(default(LogMessage), new BinaryDecoder(stream));
+            }
+            Assert.IsNotNull(deserialized);
+            Assert.AreEqual(logMessage.IP, deserialized.IP);
+            Assert.AreEqual(logMessage.message, deserialized.message);
+            Assert.AreEqual(logMessage.Severity, deserialized.Severity);
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/test/Reflect/TestReflect.cs b/lang/csharp/src/apache/test/Reflect/TestReflect.cs
new file mode 100644
index 0000000..67d9b3e
--- /dev/null
+++ b/lang/csharp/src/apache/test/Reflect/TestReflect.cs
@@ -0,0 +1,170 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Collections;
+using System.IO;
+using System.Linq;
+using NUnit.Framework;
+using Avro.IO;
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using Avro;
+using Avro.Reflect;
+using System.Reflection;
+
+namespace Avro.Test
+{
+    [TestFixture]
+    class TestReflect
+    {
+
+        enum EnumResolutionEnum
+        {
+            THIRD,
+            FIRST,
+            SECOND
+        }
+
+        class EnumResolutionRecord
+        {
+            public EnumResolutionEnum enumType { get; set; }
+        }
+
+        [TestCase]
+        public void TestEnumResolution()
+        {
+            Schema writerSchema = Schema.Parse("{\"type\":\"record\",\"name\":\"EnumRecord\",\"namespace\":\"Avro.Test\"," +
+                                        "\"fields\":[{\"name\":\"enumType\",\"type\": { \"type\": \"enum\", \"name\": \"EnumType\", \"symbols\": [\"FIRST\", \"SECOND\"]} }]}");
+
+            var testRecord = new EnumResolutionRecord();
+
+            Schema readerSchema = Schema.Parse("{\"type\":\"record\",\"name\":\"EnumRecord\",\"namespace\":\"Avro.Test\"," +
+                                        "\"fields\":[{\"name\":\"enumType\",\"type\": { \"type\": \"enum\", \"name\":" +
+                                        " \"EnumType\", \"symbols\": [\"THIRD\", \"FIRST\", \"SECOND\"]} }]}");;
+            testRecord.enumType = EnumResolutionEnum.SECOND;
+
+            // serialize
+            var stream = serialize(writerSchema, testRecord);
+
+            // deserialize
+            var rec2 = deserialize<EnumResolutionRecord>(stream, writerSchema, readerSchema);
+            Assert.AreEqual( EnumResolutionEnum.SECOND, rec2.enumType );
+        }
+
+        private static S deserialize<S>(Stream ms, Schema ws, Schema rs) where S : class
+        {
+            long initialPos = ms.Position;
+            var r = new ReflectReader<S>(ws, rs);
+            Decoder d = new BinaryDecoder(ms);
+            S output = r.Read(null, d);
+            Assert.AreEqual(ms.Length, ms.Position); // Ensure we have read everything.
+            checkAlternateDeserializers(output, ms, initialPos, ws, rs);
+            return output;
+        }
+
+        private static void checkAlternateDeserializers<S>(S expected, Stream input, long startPos, Schema ws, Schema rs) where S : class
+        {
+            input.Position = startPos;
+            var reader = new ReflectReader<S>(ws, rs);
+            Decoder d = new BinaryDecoder(input);
+            S output = reader.Read(null, d);
+            Assert.AreEqual(input.Length, input.Position); // Ensure we have read everything.
+            AssertReflectRecordEqual(rs, expected, ws, output, reader.Reader.ClassCache);
+        }
+
+        private static Stream serialize<T>(Schema ws, T actual)
+        {
+            var ms = new MemoryStream();
+            Encoder e = new BinaryEncoder(ms);
+            var w = new ReflectWriter<T>(ws);
+            w.Write(actual, e);
+            ms.Flush();
+            ms.Position = 0;
+            checkAlternateSerializers(ms.ToArray(), actual, ws);
+            return ms;
+        }
+
+        private static void checkAlternateSerializers<T>(byte[] expected, T value, Schema ws)
+        {
+            var ms = new MemoryStream();
+            var writer = new ReflectWriter<T>(ws);
+            var e = new BinaryEncoder(ms);
+            writer.Write(value, e);
+            var output = ms.ToArray();
+
+            Assert.AreEqual(expected.Length, output.Length);
+            Assert.True(expected.SequenceEqual(output));
+        }
+
+        private static void AssertReflectRecordEqual(Schema schema1, object rec1, Schema schema2, object rec2, ClassCache cache)
+        {
+            var recordSchema = (RecordSchema) schema1;
+            foreach (var f in recordSchema.Fields)
+            {
+                var rec1Val = cache.GetClass(recordSchema).GetValue(rec1, f);
+                var rec2Val = cache.GetClass(recordSchema).GetValue(rec2, f);
+                if (rec1Val.GetType().IsClass)
+                {
+                    AssertReflectRecordEqual(f.Schema, rec1Val, f.Schema, rec2Val, cache);
+                }
+                else if (rec1Val is IList)
+                {
+                    var schema1List = f.Schema as ArraySchema;
+                    var rec1List = (IList) rec1Val;
+                    if( rec1List.Count > 0 )
+                    {
+                        var rec2List = (IList) rec2Val;
+                        Assert.AreEqual(rec1List.Count, rec2List.Count);
+                        for (int j = 0; j < rec1List.Count; j++)
+                        {
+                            AssertReflectRecordEqual(schema1List.ItemSchema, rec1List[j], schema1List.ItemSchema, rec2List[j], cache);
+                        }
+                    }
+                    else
+                    {
+                        Assert.AreEqual(rec1Val, rec2Val);
+                    }
+                }
+                else if (rec1Val is IDictionary)
+                {
+                    var schema1Map = f.Schema as MapSchema;
+                    var rec1Dict = (IDictionary) rec1Val;
+                    var rec2Dict = (IDictionary) rec2Val;
+                    Assert.AreEqual(rec2Dict.Count, rec2Dict.Count);
+                    foreach (var key in rec1Dict.Keys)
+                    {
+                        var val1 = rec1Dict[key];
+                        var val2 = rec2Dict[key];
+                        if (f.Schema is RecordSchema)
+                        {
+                            AssertReflectRecordEqual(f.Schema as RecordSchema, val1, f.Schema as RecordSchema, val2, cache);
+                        }
+                        else
+                        {
+                            Assert.AreEqual(val1, val2);
+                        }
+                    }
+                }
+                else
+                {
+                    Assert.AreEqual(rec1Val, rec2Val);
+                }
+            }
+        }
+    }
+}


Mime
View raw message