Home

Request : Minor change to ToExpando to optionally allow private members to be returned.

Short version

I know the source is at https://www.linqpad.net/ToExpando.txt and so I can easily copy and amend it (as I have done), but it would be nicer if this was built in and perhaps others would find it useful.

Longer version
Uncapsulate() can be used to expose private and internal members, but does not always work.

I have included some code that illustrates what I am trying to do.

public class Customer1
{
    public string FirstName;
    public string LastName;
    public DateTime BirthDate;
    internal int internalField;
    internal int internalProperty { get; set; }
}

public class Customer2
{
    public string FirstName;
    public string LastName;
    public DateTime BirthDate;
    internal int internalField;
    internal int internalProperty { get; set; }
    
    object ToDump() => Util.ToExpando(this, exclude: "BirthDate");
}

void Main()
{
    var c1 = new Customer1() { FirstName = "Joe", LastName = "Bloggs", BirthDate = new DateTime(2001, 1, 1) };
    var c2 = new Customer2() { FirstName = "Joe", LastName = "Bloggs", BirthDate = new DateTime(2001, 1, 1) };

    // then I can dump the public members using

    c1.Dump("Public members only");

    // and if I want to include the private or internal members, I can call

    c1.Uncapsulate().Dump("Includes Private members");

    // However, this now longer works if the Customer class contains a customised ToDump method , eg

    c2.Dump("Public members only, but excluding BirthDate");
    c2.Uncapsulate().Dump("Same as above and doesn't show private/internal members");

    // I can override this custom ToDump() command by calling ToExpando

    Util.ToExpando(c2).Dump("ToExpando overrides the custom ToDump() and shows all public properties");

    // I can use ToExpando to show internal and private fields if I know the name, eg

    Util.ToExpando(c2, include: "FirstName,LastName,BirthDate,internalField").Dump("includes named internal field");

    // but not internal or private properties , ie

    Console.WriteLine("But I can not include an internal property as the following throws a error because IsInstance is only looking for the public accessors");
    Util.Try (() => Util.ToExpando(c2, include: "FirstName,LastName,BirthDate,internalField,internalProperty").Dump() , true);


    Console.WriteLine("Using a slightly amended version of ToExpando, I can then get all members without needing to know all their names");

    ToExpando(c2, includePrivate:true).Dump(); 
}




// Amended ToExpando based on https://www.linqpad.net/ToExpando.txt
// Can now optionally include private and internal members but excludes Compiler Generated members

/* Copyright (c) 2016 JOSEPH ALBAHARI

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
    files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 
    modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
    is furnished to do so, subject to the following condition:

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
    IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

public static object ToExpando(object value, string include = null, string exclude = null, bool nameOrder = false, bool includePrivate = false)
{
    if (value == null) return null;

    string[] includeMembers = (include ?? "").Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
    string[] excludeMembers = (exclude ?? "").Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

    var type = value.GetType();

    var memberInfos = includeMembers.Any()
       ? includeMembers.SelectMany(name => type.GetMember(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
       : value.GetType().GetMembers(BindingFlags.Instance | BindingFlags.Public | (includePrivate ? BindingFlags.NonPublic : BindingFlags.Default)).Where(m => m is PropertyInfo || m is FieldInfo);

    if (excludeMembers.Any())
        memberInfos = memberInfos.Where(m => !excludeMembers.Contains(m.Name));

    if (nameOrder)
        memberInfos = memberInfos.OrderBy(m => m.Name);

    return ToExpando(value, memberInfos.ToArray());
    
}

public static object ToExpando(object value, IEnumerable include)
{
    IDictionary expando = new System.Dynamic.ExpandoObject();

    foreach (var member in include.Where(IsInstance))
        try
        {
            if (member is FieldInfo fi && !fi.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
                expando[member.Name] = fi.GetValue(value);
            else if (member is PropertyInfo pi &&  pi.GetIndexParameters().Length == 0)
                expando[member.Name] = pi.GetValue(value);
            else if (member is MethodInfo mi && mi.GetParameters().Length == 0 &&  !mi.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
                expando[member.Name] = mi.Invoke(value, null);
            // Ignore other kinds of member
        }
        catch (Exception ex)   // If it faults, record the error in the expando.
        {
            expando.Add(member.Name, ex.GetBaseException());
        }

    return expando;
}

static bool IsInstance(MemberInfo mi) =>
   mi is FieldInfo ? (((FieldInfo)mi).Attributes & FieldAttributes.Static) != FieldAttributes.Static :
   mi is PropertyInfo ? !((PropertyInfo)mi).GetAccessors(true).First().IsStatic :
   mi is MethodInfo ? !((MethodInfo)mi).IsStatic :
   false;   // We're interested only in fields, properties and methods.     

Comments

  • edited June 9

    I find that implementing a proxy using a meta object makes extending this way far more flexible. I had an implementation somewhere but lost track of it. I think what I did was implemented a dynamic object backed by an expando object. You could apply interfaces to it so it would behave as such as long as an implementation of the member is provided. But importantly, it provided access to all members of the object it was wrapping. If I can find it I'll share it here. Or maybe it'll give you inspiration to implement one. :)

    I had another version of this with what I described but this could be used as a jumping off point.
    https://stackoverflow.com/a/32419466

  • I have to be honest, I don't quite understand.

    What is the difference between what you are describing and what Uncapsulator does?

  • I guess this provides more options, in that after an object has been converted to an expando, you can add/remove members. It should be a simple addition - will aim for 7.4.4.

Sign In or Register to comment.