Skip to content

Commit

Permalink
Added EnableImplicitTypingByInspecting<T> extension method (#495)
Browse files Browse the repository at this point in the history
  • Loading branch information
onetocny authored Dec 12, 2020
1 parent cd3a8c5 commit fab9f9e
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 0 deletions.
28 changes: 28 additions & 0 deletions src/ExtendedXmlSerializer/ExtensionMethodsForImplicitTyping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,5 +197,33 @@ public static IConfigurationContainer EnableImplicitTypingFromNamespacePublic<T>
public static IConfigurationContainer EnableImplicitTypingFromNamespacePublic(
this IConfigurationContainer @this, Type type)
=> @this.EnableImplicitTyping(new PublicTypesInSameNamespace(type));

/// <summary>
/// Convenience method to enable implicit typing on a container, using all recursively found public property types within the
/// provided type. All public property types found within the provided type will be included and registered as an implicit type.
/// Use this with care and ensure that the names of all the public types found within the namespace are unique. Otherwise,
/// an exception will be thrown if more than one type share the same name.
/// </summary>
/// <typeparam name="T">The subject type to query for type resolution.</typeparam>
/// <param name="this">The configuration container to configure.</param>
/// <returns>The configured configuration container.</returns>
/// <seealso cref="EnableImplicitTyping(IConfigurationContainer,System.Type[])"/>
public static IConfigurationContainer EnableImplicitTypingByInspecting<T>(
this IConfigurationContainer @this)
=> @this.EnableImplicitTyping(new InspectedPropertyTypes<T>());

/// <summary>
/// Convenience method to enable implicit typing on a container, using all recursively found public property types within the
/// provided type. All public property types found within the provided type will be included and registered as an implicit type.
/// Use this with care and ensure that the names of all the public types found within the namespace are unique. Otherwise,
/// an exception will be thrown if more than one type share the same name.
/// </summary>
/// <param name="this">The configuration container to configure.</param>
/// <param name="type">The subject type to query for type resolution.</param>
/// <returns>The configured configuration container.</returns>
/// <seealso cref="EnableImplicitTyping(IConfigurationContainer,System.Type[])"/>
public static IConfigurationContainer EnableImplicitTypingByInspecting(
this IConfigurationContainer @this, Type type)
=> @this.EnableImplicitTyping(new InspectedPropertyTypes(type));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using ExtendedXmlSerializer.Core.Sources;

namespace ExtendedXmlSerializer.ExtensionModel.Types.Sources
{
/// <summary>
/// Iterates through all public properties in given type and resolve its types. Then recursively iterates through them.
/// </summary>
/// <typeparam name="T">The type to query.</typeparam>
public sealed class InspectedPropertyTypes<T> : Items<Type>
{
/// <summary>
/// Creates a new instance.
/// </summary>
public InspectedPropertyTypes() : base(new InspectedPropertyTypes(typeof(T)))
{

}
}

/// <summary>
/// Iterates through all public properties in given type and resolve its types. Then recursively iterates through them.
/// </summary>
public sealed class InspectedPropertyTypes : Items<Type>
{
readonly static HashSet<Type> IgnoredTypes = new HashSet<Type>
{
typeof(string),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
typeof(double),
typeof(bool),
typeof(decimal),
typeof(byte),
typeof(sbyte),
typeof(DateTime)
};

/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="type">The reference type to query.</param>
public InspectedPropertyTypes(Type type) : base(GetPropertyTypes(type))
{

}

static IEnumerable<Type> GetPropertyTypes(Type type)
{
var result = new HashSet<Type> { type };

var properties = type.GetProperties()
.Where(p => !IgnoredTypes.Contains(p.PropertyType))
.Where(p => p.GetCustomAttribute<XmlIgnoreAttribute>() == null);

foreach (var property in properties)
{
var propertyType = property.PropertyType;
if (IsSupportedGenericType(propertyType))
{
propertyType = propertyType.GenericTypeArguments.First();
}

if (!result.Contains(propertyType))
{
result.UnionWith(GetPropertyTypes(propertyType));
}
}

return result;
}

static bool IsSupportedGenericType(Type propertyType)
{
return typeof(IEnumerable).IsAssignableFrom(propertyType) && propertyType.IsGenericType && propertyType.GenericTypeArguments.Length == 1;
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using ExtendedXmlSerializer.ExtensionModel.Types.Sources;
using Xunit;

namespace ExtendedXmlSerializer.Tests.ExtensionModel.Types.Sources
{
public class InspectedPropertyTypesTests
{
[Fact]
public void ShouldHandleSingleProperty()
{
var types = new InspectedPropertyTypes<Vehicle>();

var result = types.Get();

Assert.Equal(2, result.Length);
Assert.Contains(result, t => t == typeof(Vehicle));
Assert.Contains(result, t => t == typeof(Engine));
}


[Fact]
public void ShouldIgnoreExcludedProperties()
{
var types = new InspectedPropertyTypes<AlmostEmptyClass>();

var result = types.Get();

Assert.Single(result);
Assert.Contains(result, t => t == typeof(AlmostEmptyClass));
}



[Fact]
public void SamePropertyAsParentShouldNotCauseStackOverflow()
{
var types = new InspectedPropertyTypes<Human>();

var result = types.Get();

Assert.Single(result);
Assert.Contains(result, t => t == typeof(Human));
}

[Fact]
public void ShouldHandleListGracefully()
{
var types = new InspectedPropertyTypes<Zoo>();

var result = types.Get();

Assert.Equal(2, result.Length);
Assert.Contains(result, t => t == typeof(Zoo));
Assert.Contains(result, t => t == typeof(Animal));
}

class Vehicle
{
public Engine Engine { get; set; }
}

class Engine
{
}

class AlmostEmptyClass
{
public int Int { get; set; }
public uint UInt { get; set; }
public string String { get; set; }
public long Long { get; set; }
public ulong ULong { get; set; }
public short Short { get; set; }
public ushort UShort { get; set; }
public double Double { get; set; }
public bool Bool { get; set; }
public decimal Decimal { get; set; }
public byte Byte { get; set; }
public sbyte SByte { get; set; }
public DateTime DateTime { get; set; }

[XmlIgnore]
public Animal Animal { get; set; }
}

class Human
{
public Human Mother { get; set; }
public Human Father { get; set; }
}

class Zoo
{
public List<Animal> Animals { get; set; }
}

class Animal
{
}
}


}

0 comments on commit fab9f9e

Please sign in to comment.