Skip to content

Commit

Permalink
Fix/138/readonly properties (#140)
Browse files Browse the repository at this point in the history
* added test and implementation draft. Need to workaround fastmember I think

* copied FastMember and applied necessary changes
  • Loading branch information
AdrianStrugala authored Nov 28, 2023
1 parent f07c71c commit 9340505
Show file tree
Hide file tree
Showing 22 changed files with 2,583 additions and 13 deletions.
29 changes: 29 additions & 0 deletions AvroConvert.sln
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBenchmarks", "tests\Cor
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvroConvertOnline", "src\SolTechnology.AvroConvertOnline\AvroConvertOnline.csproj", "{5902493F-818A-4897-A132-4DAED2560E7F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolTechnology.FastMember", "src\SolTechnology.FastMember\SolTechnology.FastMember.csproj", "{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SolTechnology.FastMember.UnitTests", "tests\SolTechnology.FastMember.UnitTests\SolTechnology.FastMember.UnitTests.csproj", "{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -215,6 +219,30 @@ Global
{5902493F-818A-4897-A132-4DAED2560E7F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{5902493F-818A-4897-A132-4DAED2560E7F}.Release|x86.ActiveCfg = Release|Any CPU
{5902493F-818A-4897-A132-4DAED2560E7F}.Release|x86.Build.0 = Release|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Debug|x86.Build.0 = Debug|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Release|Any CPU.Build.0 = Release|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Release|x86.ActiveCfg = Release|Any CPU
{1A63C9F6-6D17-43DB-92F2-F4ECBC201D2E}.Release|x86.Build.0 = Release|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Debug|x86.ActiveCfg = Debug|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Debug|x86.Build.0 = Debug|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Release|Any CPU.Build.0 = Release|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Release|x86.ActiveCfg = Release|Any CPU
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -229,6 +257,7 @@ Global
{E4D7C0E6-D268-4B3D-A654-D86235828940} = {CCBD5998-CCF4-48BA-B80E-716F68613375}
{F2E89AF6-EFDD-4233-A15D-3AE4D5CFE48F} = {78EEDAB3-E0F3-4BDC-AB47-C5FBF60417B8}
{ADAE7FF2-F659-43D8-B826-D6D2C477E596} = {78EEDAB3-E0F3-4BDC-AB47-C5FBF60417B8}
{A7DE20B8-8E4F-4F89-860D-6404C365EFB4} = {CCBD5998-CCF4-48BA-B80E-716F68613375}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F9BF1700-C9CB-438A-B3CD-601762B9DCB2}
Expand Down
14 changes: 12 additions & 2 deletions src/AvroConvert/AvroConvert.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,26 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.*" />
<PackageReference Include="IronSnappy" Version="1.3.*" />
<PackageReference Include="BrotliSharpLib" Version="0.3.*" />
<PackageReference Include="FastMember" Version="1.5.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.*" />
<PackageReference Include="Portable.System.DateTimeOnly" Version="6.0.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<PackageReference Include="Newtonsoft.Json" Version="13.0.*" />
<PackageReference Include="IronSnappy" Version="1.3.*" />
<PackageReference Include="BrotliSharpLib" Version="0.3.*" />
<PackageReference Include="FastMember" Version="1.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SolTechnology.FastMember\SolTechnology.FastMember.csproj" PrivateAssets="All" />
</ItemGroup>

<!-- To indlucde referenced projects in nuget package: -->
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>

</Project>
25 changes: 15 additions & 10 deletions src/AvroConvert/AvroObjectServices/Read/Resolvers/Record.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal partial class Resolver
private readonly Dictionary<int, Dictionary<string, ReadStep>> _readStepsDictionary = new();
private readonly Dictionary<int, TypeAccessor> _accessorDictionary = new();

protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema readerSchema, IReader dec,
protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema readerSchema, IReader reader,
Type type)
{
if (type != typeof(object))
Expand Down Expand Up @@ -58,13 +58,18 @@ protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema r
continue;
}

accessor[result, memberInfo.Name] = GetValue(wf, rf, memberInfo, dec);

readSteps.Add(memberInfo.Name, new ReadStep(wf, rf, memberInfo));

if (memberInfo.CanWrite)
{
accessor[result, memberInfo.Name] = GetValue(wf, rf, memberInfo, reader);
readSteps.Add(memberInfo.Name, new ReadStep(wf, rf, memberInfo));
}
else
{
_skipper.Skip(wf.TypeSchema, reader);
}
}
else
_skipper.Skip(wf.TypeSchema, dec);
_skipper.Skip(wf.TypeSchema, reader);
}

_readStepsDictionary.Add(typeHash, readSteps);
Expand All @@ -83,7 +88,7 @@ protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema r
readStepValue.WriteFieldSchema,
readStepValue.ReadFieldSchema,
readStepValue.MemberInfo,
dec);
reader);
}
}

Expand All @@ -104,17 +109,17 @@ protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema r
dynamic value;
if (wf.TypeSchema.Type == AvroType.Array)
{
value = Resolve(wf.TypeSchema, rf.TypeSchema, dec, typeof(List<object>)) ?? wf.DefaultValue;
value = Resolve(wf.TypeSchema, rf.TypeSchema, reader, typeof(List<object>)) ?? wf.DefaultValue;
}
else
{
value = Resolve(wf.TypeSchema, rf.TypeSchema, dec, typeof(object)) ?? wf.DefaultValue;
value = Resolve(wf.TypeSchema, rf.TypeSchema, reader, typeof(object)) ?? wf.DefaultValue;
}

result.Add(name, value);
}
else
_skipper.Skip(wf.TypeSchema, dec);
_skipper.Skip(wf.TypeSchema, reader);
}
return result;
}
Expand Down
48 changes: 48 additions & 0 deletions src/SolTechnology.FastMember/CallSiteCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Collections;
using System.Runtime.CompilerServices;
using System;
using Microsoft.CSharp.RuntimeBinder;

namespace FastMember
{
internal static class CallSiteCache
{
private static readonly Hashtable getters = new Hashtable(), setters = new Hashtable();

internal static object GetValue(string name, object target)
{
CallSite<Func<CallSite, object, object>> callSite = (CallSite<Func<CallSite, object, object>>)getters[name];
if (callSite == null)
{
CallSite<Func<CallSite, object, object>> newSite = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(CSharpBinderFlags.None, name, typeof(CallSiteCache), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
lock (getters)
{
callSite = (CallSite<Func<CallSite, object, object>>)getters[name];
if (callSite == null)
{
getters[name] = callSite = newSite;
}
}
}
return callSite.Target(callSite, target);
}
internal static void SetValue(string name, object target, object value)
{
CallSite<Func<CallSite, object, object, object>> callSite = (CallSite<Func<CallSite, object, object, object>>)setters[name];
if (callSite == null)
{
CallSite<Func<CallSite, object, object, object>> newSite = CallSite<Func<CallSite, object, object, object>>.Create(Binder.SetMember(CSharpBinderFlags.None, name, typeof(CallSiteCache), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }));
lock (setters)
{
callSite = (CallSite<Func<CallSite, object, object, object>>)setters[name];
if (callSite == null)
{
setters[name] = callSite = newSite;
}
}
}
callSite.Target(callSite, target, value);
}

}
}
150 changes: 150 additions & 0 deletions src/SolTechnology.FastMember/MemberSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace FastMember
{
/// <summary>
/// Represents an abstracted view of the members defined for a type
/// </summary>
public sealed class MemberSet : IEnumerable<Member>, IList<Member>
{
Member[] members;
internal MemberSet(Type type)
{
const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
members = type.GetTypeAndInterfaceProperties(PublicInstance).Cast<MemberInfo>().Concat(type.GetFields(PublicInstance).Cast<MemberInfo>()).OrderBy(x => x.Name)
.Select(member => new Member(member)).ToArray();
}
/// <summary>
/// Return a sequence of all defined members
/// </summary>
public IEnumerator<Member> GetEnumerator()
{
foreach (var member in members) yield return member;
}
/// <summary>
/// Get a member by index
/// </summary>
public Member this[int index]
{
get { return members[index]; }
}
/// <summary>
/// The number of members defined for this type
/// </summary>
public int Count { get { return members.Length; } }
Member IList<Member>.this[int index]
{
get { return members[index]; }
set { throw new NotSupportedException(); }
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
bool ICollection<Member>.Remove(Member item) { throw new NotSupportedException(); }
void ICollection<Member>.Add(Member item) { throw new NotSupportedException(); }
void ICollection<Member>.Clear() { throw new NotSupportedException(); }
void IList<Member>.RemoveAt(int index) { throw new NotSupportedException(); }
void IList<Member>.Insert(int index, Member item) { throw new NotSupportedException(); }

bool ICollection<Member>.Contains(Member item) => members.Contains(item);
void ICollection<Member>.CopyTo(Member[] array, int arrayIndex) { members.CopyTo(array, arrayIndex); }
bool ICollection<Member>.IsReadOnly { get { return true; } }
int IList<Member>.IndexOf(Member member) { return Array.IndexOf<Member>(members, member); }

}
/// <summary>
/// Represents an abstracted view of an individual member defined for a type
/// </summary>
public sealed class Member
{
private readonly MemberInfo member;
internal Member(MemberInfo member)
{
this.member = member;
}
/// <summary>
/// The ordinal of this member among other members.
/// Returns -1 in case the ordinal is not set.
/// </summary>
public int Ordinal
{
get
{
var ordinalAttr = member.CustomAttributes.FirstOrDefault(p => p.AttributeType == typeof(OrdinalAttribute));

if (ordinalAttr == null)
{
return -1;
}

// OrdinalAttribute class must have only one constructor with a single argument.
return Convert.ToInt32(ordinalAttr.ConstructorArguments.Single().Value);
}
}
/// <summary>
/// The name of this member
/// </summary>
public string Name { get { return member.Name; } }
/// <summary>
/// The type of value stored in this member
/// </summary>
public Type Type
{
get
{
if(member is FieldInfo) return ((FieldInfo)member).FieldType;
if (member is PropertyInfo) return ((PropertyInfo)member).PropertyType;
throw new NotSupportedException(member.GetType().Name);
}
}

/// <summary>
/// Is the attribute specified defined on this type
/// </summary>
public bool IsDefined(Type attributeType)
{
if (attributeType == null) throw new ArgumentNullException(nameof(attributeType));
return Attribute.IsDefined(member, attributeType);
}

/// <summary>
/// Getting Attribute Type
/// </summary>
public Attribute GetAttribute(Type attributeType, bool inherit)
=> Attribute.GetCustomAttribute(member, attributeType, inherit);

/// <summary>
/// Property Can Write
/// </summary>
public bool CanWrite
{
get
{
switch (member.MemberType)
{
case MemberTypes.Field: return true;
case MemberTypes.Property: return ((PropertyInfo)member).CanWrite;
default: throw new NotSupportedException(member.MemberType.ToString());
}
}
}

/// <summary>
/// Property Can Read
/// </summary>
public bool CanRead
{
get
{
switch (member.MemberType)
{
case MemberTypes.Field: return true;
case MemberTypes.Property: return ((PropertyInfo)member).CanRead;
default: throw new NotSupportedException(member.MemberType.ToString());
}
}
}
}
}
Loading

0 comments on commit 9340505

Please sign in to comment.