diff --git a/src/System.Linq.Dynamic.Core/ClassFactory.cs b/src/System.Linq.Dynamic.Core/ClassFactory.cs deleted file mode 100644 index 47d43f49..00000000 --- a/src/System.Linq.Dynamic.Core/ClassFactory.cs +++ /dev/null @@ -1,262 +0,0 @@ -//using System.Collections.Generic; -//using System.Reflection; -//using System.Reflection.Emit; -//using System.Threading; - -//namespace System.Linq.Dynamic.Core -//{ -// internal class ClassFactory -// { -// public static readonly ClassFactory Instance = new ClassFactory(); - -// private ModuleBuilder _module; -// private Dictionary _classes; -// private int _classCount; -//#if SILVERLIGHT -// ReaderWriterLock _rwLock; -//#else -// private ReaderWriterLockSlim _rwLock; -//#endif - -// private ClassFactory() -// { -// AssemblyName name = new AssemblyName("System.Linq.Dynamic.Core.DynamicClasses, Version=1.0.0.0"); -// AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); -//#if ENABLE_LINQ_PARTIAL_TRUST -// new ReflectionPermission(PermissionState.Unrestricted).Assert(); -//#endif -// try -// { -// _module = assembly.DefineDynamicModule("System.Linq.Dynamic.Core.DynamicClasses"); -// } -// finally -// { -//#if ENABLE_LINQ_PARTIAL_TRUST -// PermissionSet.RevertAssert(); -//#endif -// } -// _classes = new Dictionary(); -//#if SILVERLIGHT -// _rwLock = new ReaderWriterLock(); -//#else -// _rwLock = new ReaderWriterLockSlim(); -//#endif -// } - -// public Type CreateType(IEnumerable properties) -// { -// Signature signature = new Signature(properties); - -//#if SILVERLIGHT || NET20 -// _rwLock.AcquireReaderLock(Timeout.Infinite); -//#else -// _rwLock.EnterReadLock(); -//#endif - -// try -// { -// Type type; - -// if (_classes.TryGetValue(signature, out type)) return type; -// } -// finally -// { -//#if SILVERLIGHT -// _rwLock.ReleaseReaderLock(); -//#else -// _rwLock.ExitReadLock(); -//#endif -// } - -// return CreateDynamicClass(signature); -// } - -// private Type CreateDynamicClass(Signature signature) -// { -//#if SILVERLIGHT || NET20 -// _rwLock.UpgradeToWriterLock(Timeout.Infinite); -//#else -// _rwLock.EnterWriteLock(); -//#endif - -// try -// { -// Type type; - -// //do a final check to make sure the type hasn't been generated. -// if (_classes.TryGetValue(signature, out type)) -// return type; - -// string typeName = "DynamicClass" + (_classCount + 1); -//#if ENABLE_LINQ_PARTIAL_TRUST -// new ReflectionPermission(PermissionState.Unrestricted).Assert(); -//#endif -// try -// { -// TypeBuilder tb = _module.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, typeof(DynamicClass)); -// FieldInfo[] fields = GenerateProperties(tb, signature.properties); -// GenerateEquals(tb, fields); -// GenerateGetHashCode(tb, fields); - -// Type result = tb.CreateType(); -// _classCount++; - -// _classes.Add(signature, result); - -// return result; -// } -// finally -// { -//#if ENABLE_LINQ_PARTIAL_TRUST -// PermissionSet.RevertAssert(); -//#endif -// } -// } -// finally -// { -//#if SILVERLIGHT -// _rwLock.DowngradeToReaderLock(); -//#else -// _rwLock.ExitWriteLock(); -//#endif -// } -// } - -// private static FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties) -// { -// MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; -// FieldInfo[] fields = new FieldBuilder[properties.Length]; -// for (int i = 0; i < properties.Length; i++) -// { -// DynamicProperty dp = properties[i]; -// FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private); -// PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null); - -// MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name, getSetAttr, dp.Type, new Type[0]); -// ILGenerator genGet = mbGet.GetILGenerator(); -// genGet.Emit(OpCodes.Ldarg_0); -// genGet.Emit(OpCodes.Ldfld, fb); -// genGet.Emit(OpCodes.Ret); -// pb.SetGetMethod(mbGet); - -// MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name, getSetAttr, null, new[] { dp.Type }); - -// // workaround for https://github.com/dotnet/corefx/issues/7792 -// mbSet.DefineParameter(1, ParameterAttributes.In, "_" + dp.Name); - -// ILGenerator genSet = mbSet.GetILGenerator(); -// genSet.Emit(OpCodes.Ldarg_0); -// genSet.Emit(OpCodes.Ldarg_1); -// genSet.Emit(OpCodes.Stfld, fb); -// genSet.Emit(OpCodes.Ret); -// pb.SetSetMethod(mbSet); - -// fields[i] = fb; -// } - -// return fields; -// } - -// private static void GenerateEquals(TypeBuilder tb, FieldInfo[] fields) -// { -// MethodBuilder mb = tb.DefineMethod("Equals", -// MethodAttributes.Public | MethodAttributes.ReuseSlot | -// MethodAttributes.Virtual | MethodAttributes.HideBySig, -// typeof(bool), new Type[] { typeof(object) }); -// ILGenerator gen = mb.GetILGenerator(); -// LocalBuilder other = gen.DeclareLocal(tb.AsType()); -// Label next = gen.DefineLabel(); -// gen.Emit(OpCodes.Ldarg_1); -// gen.Emit(OpCodes.Isinst, tb.AsType()); -// gen.Emit(OpCodes.Stloc, other); -// gen.Emit(OpCodes.Ldloc, other); -// gen.Emit(OpCodes.Brtrue_S, next); -// gen.Emit(OpCodes.Ldc_I4_0); -// gen.Emit(OpCodes.Ret); -// gen.MarkLabel(next); -// foreach (FieldInfo field in fields) -// { -// Type ft = field.FieldType; -// Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); -// next = gen.DefineLabel(); -// gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); -// gen.Emit(OpCodes.Ldarg_0); -// gen.Emit(OpCodes.Ldfld, field); -// gen.Emit(OpCodes.Ldloc, other); -// gen.Emit(OpCodes.Ldfld, field); -// gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { ft, ft }), null); -// gen.Emit(OpCodes.Brtrue_S, next); -// gen.Emit(OpCodes.Ldc_I4_0); -// gen.Emit(OpCodes.Ret); -// gen.MarkLabel(next); -// } -// gen.Emit(OpCodes.Ldc_I4_1); -// gen.Emit(OpCodes.Ret); -// } - -// private static void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields) -// { -// MethodBuilder mb = tb.DefineMethod("GetHashCode", -// MethodAttributes.Public | MethodAttributes.ReuseSlot | -// MethodAttributes.Virtual | MethodAttributes.HideBySig, -// typeof(int), new Type[0]); -// ILGenerator gen = mb.GetILGenerator(); -// gen.Emit(OpCodes.Ldc_I4_0); -// foreach (FieldInfo field in fields) -// { -// Type ft = field.FieldType; -// Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); -// gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); -// gen.Emit(OpCodes.Ldarg_0); -// gen.Emit(OpCodes.Ldfld, field); -// gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new[] { ft }), null); -// gen.Emit(OpCodes.Xor); -// } -// gen.Emit(OpCodes.Ret); -// } - - -// private class Signature : IEquatable -// { -// public DynamicProperty[] properties; -// public int hashCode; - -// public Signature(IEnumerable properties) -// { -// this.properties = properties.ToArray(); -// hashCode = 0; -// foreach (DynamicProperty p in properties) -// { -// hashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode(); -// } -// } - -// public override int GetHashCode() -// { -// return hashCode; -// } - -// public override bool Equals(object obj) -// { -// var other = obj as Signature; - -// if (other != null) return Equals(other); - -// return false; -// } - -// public bool Equals(Signature other) -// { -// if (other == null) return false; -// if (properties.Length != other.properties.Length) return false; - -// for (int i = 0; i < properties.Length; i++) -// { -// if (properties[i].Name != other.properties[i].Name || -// properties[i].Type != other.properties[i].Type) return false; -// } -// return true; -// } -// } -// } -//} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/ExpressionParser.cs index 8db35c98..9a3fcd39 100644 --- a/src/System.Linq.Dynamic.Core/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/ExpressionParser.cs @@ -895,6 +895,10 @@ Expression ParsePrimary() _textParser.NextToken(); expr = ParseMemberAccess(null, expr); } + else if (_textParser.CurrentToken.Id == TokenId.NullPropagation) + { + throw new NotSupportedException("An expression tree lambda may not contain a null propagating operator"); + } else if (_textParser.CurrentToken.Id == TokenId.OpenBracket) { expr = ParseElementAccess(expr); diff --git a/src/System.Linq.Dynamic.Core/GeneratedTypesCache.cs b/src/System.Linq.Dynamic.Core/GeneratedTypesCache.cs deleted file mode 100644 index f0bfd316..00000000 --- a/src/System.Linq.Dynamic.Core/GeneratedTypesCache.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Concurrent; -using System.Threading; - -namespace System.Linq.Dynamic.Core -{ - internal class GeneratedTypesCache - { - private static int _index = -1; - - private static readonly ConcurrentDictionary GeneratedTypes = new ConcurrentDictionary(); - - internal bool TryGet(string[] propertyNames, out Type type) - { - type = null; - // Anonymous classes are generics based. The generic classes are distinguished by number of parameters and name of parameters. - // The specific types of the parameters are the generic arguments. - // We recreate this by creating a fullName composed of all the property names, separated by a "|". - string fullName = string.Join("|", propertyNames.Select(Escape).ToArray()); - - lock (GeneratedTypes) - { - if (!GeneratedTypes.TryGetValue(fullName, out type)) - { - int index = Interlocked.Increment(ref _index); - } - } - - return true; - } - - private static string Escape(string str) - { - // We escape the \ with \\, so that we can safely escape the "|" (that we use as a separator) with "\|" - str = str.Replace(@"\", @"\\"); - str = str.Replace(@"|", @"\|"); - return str; - } - } -} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs index c9d58950..2b2d38c5 100644 --- a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs +++ b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs @@ -214,6 +214,11 @@ public void NextToken() NextChar(); tokenId = TokenId.NullCoalescing; } + else if (_ch == '.') + { + NextChar(); + tokenId = TokenId.NullPropagation; + } else { tokenId = TokenId.Question; diff --git a/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs b/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs index 1d8fce1d..490a8981 100644 --- a/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs +++ b/src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs @@ -39,6 +39,7 @@ internal enum TokenId DoubleGreaterThan, DoubleLessThan, NullCoalescing, - Lambda + Lambda, + NullPropagation } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/_frameworks.txt b/src/System.Linq.Dynamic.Core/_frameworks.txt deleted file mode 100644 index ef48e34a..00000000 --- a/src/System.Linq.Dynamic.Core/_frameworks.txt +++ /dev/null @@ -1,60 +0,0 @@ -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| Name | Abbr | Description | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| .NET Framework | net | Standard .NET BCL on Windows Desktop and Server. | -| | net11 | | -| | net20 | | -| | net35 | | -| | net40 | | -| | net403 | | -| | net45 | | -| | net451 | | -| | net452 | | -| | net46 | | -| | net461 | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| .NET Core | netcore | .NET Core Framework used in Windows Store Development | -| | netcore (equivalent to netcore45) | | -| | netcore45 (equivalent to win8) | | -| | netcore451 (equivalent to win81) | | -| | netcore50 | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| .NET MicroFramework | netmf | Support for [.NET MicroFramework](http://netmf.github.io/) projects | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| Windows | win | Frameworks to support Windows Store Development | -| | win (equivalent to win8) | | -| | win8 (equivalent to netcore45) | | -| | win81 (equivalent to netcore451) | | -| | win10 -- not supported by Windows 10 Platform | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| Silverlight | sl | Support for the Silverlight frameworks | -| | sl4 | | -| | sl5 | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| Windows Phone | wp | Windows Phone application support | -| | wp (equivalent to wp7) | | -| | wp7 | | -| | wp75 | | -| | wp8 | | -| | wp81 | | -| | wpa81 | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| DNX | dnx | ASP.NET server-side framework support (Windows only) | -| | dnx (equivalent to dnx45) | | -| | dnx45 | | -| | dnx451 | | -| | dnx452 | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| DNX Core | dnxcore | Core CLR framework support | -| | dnxcore (equivalent to dnxcore50) | | -| | dnxcore50 | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| Universal Windows Platform | uap | Support for Windows 10 Universal Application Platform | -| | uap (equivalent to uap10) | | -| | uap10 | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ -| Deprecated Frameworks | aspnet50 | | -| aspnetcore50 | | | -| winrt | | | -| | | | -+-----------------------------+-----------------------------------------------+---------------------------------------------------------------------+ \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/OperatorTests.cs b/test/System.Linq.Dynamic.Core.Tests/OperatorTests.cs index 06000619..e58c7d73 100644 --- a/test/System.Linq.Dynamic.Core.Tests/OperatorTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/OperatorTests.cs @@ -1,11 +1,27 @@ using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using NFluent; using Xunit; namespace System.Linq.Dynamic.Core.Tests { public class OperatorTests { + [Fact] + public void Operator_NullPropagation() + { + // Arrange + var users = new[] + { + new User { Income = 1, Profile = new UserProfile { FirstName = "F1" } }, + new User { Income = 2, Profile = new UserProfile { FirstName = "F2" } }, + new User { Income = 3, Profile = null } + }.AsQueryable(); + + // Act + Check.ThatCode(() => users.Select("Profile?.Age")).Throws(); + } + [Fact] public void Operator_Multiplication_Single_Float_ParseException() { diff --git a/test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs index 536f0c75..58d98837 100644 --- a/test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs @@ -93,6 +93,18 @@ public void TextParser_Parse_LessGreater() Check.That(textParser.CurrentToken.Text).Equals("<>"); } + [Fact] + public void TextParser_Parse_NullPropagation() + { + // Assign + Act + var textParser = new TextParser(" ?. "); + + // Assert + Check.That(textParser.CurrentToken.Id).Equals(TokenId.NullPropagation); + Check.That(textParser.CurrentToken.Pos).Equals(1); + Check.That(textParser.CurrentToken.Text).Equals("?."); + } + [Fact] public void TextParser_Parse_RealLiteral() {