It's hard to establish best practices for something as "flexible" or abstract as a DTO. Essentially, DTOs are only objects for data transfer but depending on the destination or the reason for the transfer, you may want to apply different "best practices".
I recommend reading Patterns of Enterprise Application Architecture
by Martin Fowler. There's a whole chapter dedicated to patterns, where DTOs get a really detailed section.
Originally, they were "designed" to be used in expensive remote calls, where you would likely to need a lot of data from different parts of you logic; DTOs would make the transfer of data in a single call.
According to the author, DTOs weren't intended to be used in local environments, but some people found a use for them. Usually they're used to gather information from diferent POCOs into a single entity for GUIs, APIs or different layers.
Now, with inheritance, code reuse is more like a side-effect of inheritance rather than its main objective; composition, on the other hand, is implemented with code reuse as the main objective.
Some people recommend the use of composition and inheritance together, using the strengths of both and trying to mitigate their weaknesses. The following is part of my mental process when choosing or creating new DTOs, or any new class/object for that matter:
- I use inheritance with DTOs inside the same layer or same context. A DTO will never inherit from a POCO, a BLL DTO will never inherit from a DAL DTO, etc.
- If I find myself trying to hide a field from a DTO, I'll refactor and maybe use composition instead.
- If very few different fields from a base DTO is all I need, I'll put them in a universal DTO. Universal DTOs are used only internally.
- A base POCO/DTO will almost never be used for any logic, that way the base answers only to the needs of its children. If I ever need to use the base, I avoid adding any new field that its children will never use.
Some of them maybe not the "best" practices, they work quite well for the projects I've been working on but you need to remember that no size fits all. In the case of the universal DTO you should be careful, my methods signatures look like this:
public void DoSomething(BaseDTO base) {
//Some code
}
If any of the methods ever need its own DTO I do inheritance and usually the only change I need to make is the parameter, though sometimes I need to dig deeper for specific cases.
From your comments I get that you're using nested DTOs. If your nested DTOs consist only of a list of other DTOs, I think the best thing to do is unwrap the list.
Depending on the amount of data you need to display or to work with, it may be a good idea to create new DTOs that limit the data; for example, if your UserDTO has a lot of fields and you only need 1 or 2, it may be better to have a DTO with only those fields. Defining the layer, context, usage and utility of a DTO will help a lot when designing it.