1

Let say I have a class that is

public class ClientModel
{
    [Required]
    public int Id { get; set; }
    [Required]
    [MinLength(2, ErrorMessage = "First name can not be shorter than 2 characters")]
    public string FirstName { get; set; }
    [Required]
    [MinLength(2, ErrorMessage = "Last name can not be shorter than 2 characters")]
    public string LastName { get; set; }
    public string Email { get; set; }
}

and I want to pass a ClientModel Obj to a function but without the email value. What would be the efficient way of doing this (besides just ignoring the email in the function). Is there a way to create a new class on the spot minus the field we don't need?

Volkmar Rigo
  • 1,158
  • 18
  • 32
Tam nguyen
  • 47
  • 6
  • Create another class, but without the email. Google "c# dto object" https://stackoverflow.com/questions/14724612/data-transfer-object-pattern – Andy Sep 14 '20 at 02:21
  • Or you could create a parent class with what you need, and then `ClientModel` adds the email. – Andrew Sep 14 '20 at 02:29
  • The above would be “inheritance”, if looking for a keyword. C# works on LSP subtype polymorphism and there is no way to ‘delete’ a field as that breaks (type) substitution, so only new fields can be added in derived types, which requires switching the approach around. – user2864740 Sep 14 '20 at 02:39
  • 1
    The closest method I know of “to create a new inline type to call a function” is to pass a [ValueTuple with named items](https://stackoverflow.com/questions/43565738/name-valuetuple-properties-when-creating-with-new). I _don’t_ recommend it here, although it’s suitable in some cases. – user2864740 Sep 14 '20 at 02:42
  • 1
    OP, I notice you are using the word "attribute" in the title and "field" in the body of your question. Both of those terms actually mean something else. The word you need here is "property," at least if you are talking about `Email` in your example. – John Wu Sep 14 '20 at 03:17
  • Are you open to solutions that involve changing `ClientModel` Normally we do not try to remove the definition of the property, but through serialization we can omit or deliberately blank fields from the response. If you provide more information on the overall scenario you might get a more appropriate response, have a read through this article, sounds like you have an XY problem: http://xyproblem.info/ – Chris Schaller Sep 14 '20 at 05:33
  • You really need to include a code example of the method that you do not want to have access to the `Email` property. It helps explain your scenario – Chris Schaller Sep 14 '20 at 05:45
  • You need to invert your thought as suggested by @ChrisSchaller: you asked **how to generalize a class** *by deriving from it*. I hope this can help you to enjoy C# coding: [How do I improve my knowledge in C#](http://www.ordisoftware.com/files/stack-overflow/CsharpBegin.htm) –  Sep 14 '20 at 06:12

1 Answers1

5

OP Please read through XY Problem, I strongly suspect there is a better solution to your problem than what you are asking for here.

Type Inheritance

One way to achieve this is through inheritance, but it's really the reverse of what you expect, a child type in c# cannot remove the definition of members that are defined in the parent type, so in this solution we make ClientModel the child!

  1. Rename your current class to ClientModelBase
  2. Add a new class called ClientModel that inherits from ClientModelBase
  3. Move the email property from ClientModelBase to ClientModel

The result looks like this:

public class ClientModelBase
{
    [Required]
    public int Id { get; set; }
    [Required]
    [MinLength(2, ErrorMessage = "First name can not be shorter than 2 characters")]
    public string FirstName { get; set; }
    [Required]
    [MinLength(2, ErrorMessage = "Last name can not be shorter than 2 characters")]
    public string LastName { get; set; }
}

public class ClientModel : ClientModelBase
{
    public string Email { get; set; }
}

Now at runtime, you can cast instances for ClientModel to ClientModelBase, so for methods that require the ClientModel without the Email property, you set the argument type to ClientModelBase.

// Get an instance of a client
ClientModel client = clientRepo.ByKey(123);
ProcessClientWithoutEmail(client);
...
public void ProcessClientWithoutEmail(ClientModelBase client)
{
    // client doesn't expose an Email property
}

This implicit cast technique is similar to C# value boxing, the Email property is still technically accessible if you need it later in your logic chain, we have effectively applied a mask to the object so that the compiler only sees it as the base type, when or if you need to, and only if the original instance was ClientModel you can cast the reference back to ClientModel that does have the Email property.


Interfaces [Composition]

Instead of creating a Base Class for ClientModel to inherit from, you could also use an interface. This however requires a bit more code to acheive as you need to fully define the prototype of the interface. The end result is similar, it is less imposing on your original model as the only change is to declare that ClientMode implements the new IClientModelName interface:

public class ClientModel : IClientModelName
{
    [Required]
    public int Id { get; set; }
    [Required]
    [MinLength(2, ErrorMessage = "First name can not be shorter than 2 characters")]
    public string FirstName { get; set; }
    [Required]
    [MinLength(2, ErrorMessage = "Last name can not be shorter than 2 characters")]
    public string LastName { get; set; }
    public string Email { get; set; }
}

public interface IClientModelName
{
    int Id { get; set; }
    string FirstName { get; set; }
    string LastName { get; set; }
}

This is almost identical to the previous code example, with an important difference, whilst it is still possible to Cast back to ClientModel, we can now pass objects through that have nothing to do with the repo that ClientModel came from, so this simplifies unit testing or mocking.

If your production code already has complicated inheritance or other factors that affect initialization of the you may find a Composition based solution using Interfaces fits your use case with less effort.

// Get an instance of a client
ClientModel client = clientRepo.ByKey(123);
ProcessClientWithoutEmail(client);
...
public void ProcessClientWithoutEmail(IClientModelName client)
{
    // client doesn't expose an Email property
}
Chris Schaller
  • 13,704
  • 3
  • 43
  • 81