0

I have a generic method, I give it any object of type T and a list of properties and it will return the object with the properties defined by the list set to null

Here's my code

class Program
{
    static void Main(string[] args)
    {
        var orderDto = new OrderDto();
        orderDto.Nominal = "1";
        orderDto.OrderId = "2";
        orderDto.Type = "3";

        var clean = FieldCleaner.Clean(orderDto, "OrderId");
    }
}

public class FieldCleaner
{
    public static T Clean<T>(T dto, params string[] properties) // I want in compilation time, have autocompletion that tell user the value of properties can only be a property name of the type T
    {
        var propertyInfos = dto.GetType().GetProperties();
        foreach (var propertyInfo in propertyInfos)
        {
            foreach (var property in properties)
            {
                if (propertyInfo.Name == property)
                {
                    propertyInfo.SetValue(dto, null);
                }
            }
        }

        return dto;
    }
}

public class OrderDto
{
    public string OrderId { get; set; }
    public string Nominal { get; set; }
    public string Type { get; set; }
}

My question is in the comment above in the code. I don't like the type string[], I want something like a keyof T in typescript

Im using last C# version with last .NET core

John
  • 4,351
  • 9
  • 41
  • 57
  • 1
    Please limit yourself to a single answerable question. For your main question, you could use an `params Expression>[] properties` and get the list of properties that way. Or, you know, just set the property directly. – Heretic Monkey Mar 28 '19 at 00:14
  • 1
    Performance: reflection isn't really that slow - have you measured this code and found it "too slow"? You could certainly optimize that nested loop though. – Blorgbeard Mar 28 '19 at 00:14
  • 1
    You can certainly optimize speed if you simply set the properties to null “the old fashioned way”: orderDto.OrderId = null; (and you’ll have IntelliSense too). It’s not clear to me why you feel you need to do this in some method. – Dave M Mar 28 '19 at 00:19
  • @HereticMonkey could you provide an example please ? – John Mar 28 '19 at 21:44
  • Im gonna remove my second question how performance, that's a lot less important than my question 1 – John Mar 28 '19 at 21:44
  • You can't quite do this because not all types in c# are [Nullable](https://learn.microsoft.com/en-us/dotnet/api/system.nullable?view=netframework-4.7.2). Secondly, because .net is different than javascript (by a long shot) you can do the second type, but it's literally going to be very difficult because you *have to* specify the type of each property. – Erik Philips Mar 28 '19 at 21:48
  • See https://stackoverflow.com/q/671968/215552. But @ErikPhilips provides a good warning here: you'll only be able to use this for nullable types. You could set it to its type's default value (via https://stackoverflow.com/q/325426/215552), which would be null for nullable types. – Heretic Monkey Mar 28 '19 at 22:22
  • @ErikPhilips `default(T)` is a reasonable work-around. – Joel Coehoorn Mar 28 '19 at 22:23
  • @HereticMonkey that requires knowing the type of the property defined in the method signature. Typescript does not require knowing the type, only that the property exists. (Maybe I'm wrong.. I remember doing this once and I had to write out all the types before hand, I can't remember why). – Erik Philips Mar 29 '19 at 04:02
  • @JoelCoehoorn indeed, but that's quite that easy either, unless he uses the `Expression>` so there is a `default(TProp)` to use. `default(T)` would give him a default instance, not a default property value. – Erik Philips Mar 29 '19 at 04:07
  • @Erik what typescript does is not relevant to the question, and you know the type of the property via PropertyInfo.PropertyType. The linked questions demonstrate how. – Heretic Monkey Mar 29 '19 at 12:31

2 Answers2

1

To paraphrase a bit:

// I want an auto-completion list in Visual Studio to tell the programmer what properties of T are available

The problem is the type T could be anything. You have to be able to use this code in a completely different assembly, where neither Visual Studio nor the compiler know about your T at compile time.

I won't say it's impossible. Visual Studio is very extensible, especially now we have Roslyn. But that is what you'll have to do: a custom Visual Studio extension using Roslyn to analyze the code and provide the completion list. This isn't built in to the platform.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
0

While the following answer does what you want, there really isn't a point because it's just unnecessary. If you have to specify the property, you might as well just set it directly.

Here is probably as close as you can get. I don't believe you can use params because that would require all TProp to be the same type.

In visual studio when you get to the period, you'll get intellisense:

enter image description here

using System;
using System.Linq.Expressions;
using System.Reflection;

public class Program
{
    public static void Main()
    {
        var orderDto = new OrderDto
        {
            Id = 1,
            Name = "Name",
            CreatedOn = DateTime.UtcNow,
            CompletedOn = DateTime.UtcNow.AddMinutes(30),
            Misc = Guid.NewGuid()
        };

        Console.WriteLine($"{orderDto.Id} {orderDto.Name} {orderDto.CreatedOn} {orderDto.CompletedOn} {orderDto.Misc}");

        orderDto.DefaultFor(x => x.Id, x => x.Name, x => x.CreatedOn, x => x.CompletedOn);

        Console.WriteLine($"{orderDto.Id} {orderDto.Name} {orderDto.CreatedOn} {orderDto.CompletedOn} {orderDto.Misc}");
    }
}

public static class ObjectExtensions
{
    public static void DefaultFor<TObject, TProp1, TProp2, TProp3, TProp4>(this TObject instance, 
        Expression<Func<TObject, TProp1>> selector1, 
        Expression<Func<TObject, TProp2>> selector2,
        Expression<Func<TObject, TProp3>> selector3,
        Expression<Func<TObject, TProp4>> selector4)
        where TObject : class
    {
        DefaultFor(instance, selector1, selector2, selector3);
        DefaultFor(instance, selector4);
    }

    public static void DefaultFor<TObject, TProp1, TProp2, TProp3>(this TObject instance, 
        Expression<Func<TObject, TProp1>> selector1, 
        Expression<Func<TObject, TProp2>> selector2,
        Expression<Func<TObject, TProp3>> selector3)
        where TObject : class
    {
        DefaultFor(instance, selector1, selector2);
        DefaultFor(instance, selector3);
    }

    public static void DefaultFor<TObject, TProp1, TProp2>(this TObject instance, 
        Expression<Func<TObject, TProp1>> selector1, 
        Expression<Func<TObject, TProp2>> selector2)
        where TObject : class
    {
        DefaultFor(instance, selector1);
        DefaultFor(instance, selector2);
    }

    public static void DefaultFor<TObject, TProp>(this TObject instance, Expression<Func<TObject, TProp>> selector)
        where TObject : class
    {
        if (instance == null)
            throw new ArgumentNullException();

        var memberExpression = (MemberExpression)selector.Body;
        var property = (PropertyInfo)memberExpression.Member;

        property.SetValue(instance, default(TProp));
    }
}

public class OrderDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedOn { get; set; }
    public DateTime? CompletedOn { get; set; }
    public Guid Misc { get; set; }
}

DotNetFiddle Example

Output:

1 Name 3/29/2019 5:14:06 AM 3/29/2019 5:44:06 AM 3800be41-7fe1-42da-ada5-4fe33ac04a84

0 1/1/0001 12:00:00 AM 3800be41-7fe1-42da-ada5-4fe33ac04a84

Community
  • 1
  • 1
Erik Philips
  • 53,428
  • 11
  • 128
  • 150