NAdapter
is a dynamic adapter library for the .NET Framework. It is intended for use cases where you have a core model that should not be changed, but you also want to expose that model to another library that examines its clients via reflection and you need it to look a little different from the actual core model. For example, it could be useful to satisfy a serializer that offers little customizability, or to expose the model to a scripting engine in a language whose conventions are different from C#'s.
NAdapter
is available on NuGet for .NET Framework 4.8, .NET 5.0, and .NET Standard 2.1. Lower versions of .NET may be supported in the future.
> Install-Package NAdapter
For examples of how to use the library and how to build your own adapters, see the QuickStart.cs
file in the test project.
The NAdapter
test project uses xUnit.net. You should use the xUnit test adapter to run the tests.
At present, there is no fixed process for contributing to NAdapter
. If you wish to contribute or to suggest improvement items, feel free to contact the author on GitHub.
Use Semantic Versioning for versioning. For the versions available, see the tags on this repository.
- Keith Anders - Principal development - Kanders
- Inspiration for public-facing API taken from both Castle Proxy and Moq.
I wanted to serialize a public-API-facing object with a new serializer. The problem was that the serializer I wanted to use (for performance reasons) doesn't support all the customization of the serializer I've been using so far. In order to get the object to serialize the way I need it to in the new serializer, I would need to create an adapter class that is almost identical to my public object but changes some property names, removes some properties entirely, and adds some methods with certain behaviors so the serializer can understand what I want to do. Given the hundreds of properties in the public object, I did not look forward to maintaining the link between the public class and its adapter. There had to be a better way.
I later found myself exposing a public object to a scripting engine. But I wanted to log the getters, setters, and method calls made by the script and change the method and property names from PascalCase to camelCase. But I didn't want to create an adapter class that did all that, because I'd have to manually keep the adapter's properties in sync whenever I add a property to the public object. And not all the properties and methods I want to log are virtual, so Castle Dynamic Proxy wouldn't work for me either. There had to be a better way.
So I invented one. NAdapter
is an open-source library for the .NET Framework which allows you to create adapter classes at runtime, thus replacing the manual development time of keeping adapter classes in sync with simple logic.
Q: What is the plan for this library?
A: See the Roadmap section of this document.
Q: Don't other libraries already do all this?
A: Maybe, but not that I've been able to find. All the proxy libraries I found, including Castle Dynamic Proxy work by having the proxy implement an interface or extend a base class. As such, they are only able to intercept or reimplement the virtual members. I wanted a full adapter pattern, where I have control over every member, even non-virtual ones, to modify it in arbitrary ways, including changing the name, before the type is created.
Q: Can the adapter implement an interface or extend a base class?
A: At some point that functionality may be added. But that is the edge case, not the use case, for the foreseeable future. If you want an adapter around a T
that actually is a T
, you would probably be better served by Castle Proxies than NAdapter
. This inheritance-less strategy also serves the idea of favoring composition over inheritance.
Q: So what is the use for NAdapter
, a proxy that does not implement an interface or extend a base class? How will you ever be able to access the members?
A: It is intended for consumption by code that examines its clients via reflection (such as many serializers) or by assigning to the dynamic
keyword (such as various scripting engines). Those are the only ways to access the members. It may also become a code-generation tool at some point. (see the Distant Future section of the roadmap)
Q: Why wouldn't you use an ExpandoObject
in that case?
A: If you're going to be serializing a thousand instances of a class and you need to make the exact same change to the structure of each of them, it will be much cheaper to instantiate an adapter that was specifically designed to work with that class than to create a thousand ExpandoObjects and manually populate them all correctly. Since, as far as I know, there is no built-in support for creating an ExpandoObject
based on a template or existing class (and even if there was, an ExpandoObject
would not have the same performance as a class specifically built to work with your model), a dynamically created adapter class remains the best choice.
- Methods can only have up to six parameters. In order to support more, I would have to add generic overloads of a number of classes and functions. This isn't too bad in itself, but--as the API is still evolving--adding twice as many overloads would mean twice as much code to refactor every time the API changes significantly. At this stage, keeping the API small and nimble is the higher priority.
- The contents of a property's Decoration are reset (including attributes and readonly/writeonly-ness) when you change the property's behavior. As such, make sure that the
Decoration()
changes are the last changes you make to a property. - Make sure to use fluent syntax for working with properties, or you may not have a handle to the thing you want to have a handle to.
- If you
SpecifyProperty
by a linq expression and the name matches an existing property but the type doesn't, you will get null back instead of the actual property, even if you set the behavior to AddOrGet. This should only happen if you've already accessed that same property and changed its type. It is because the object you get back is strongly typed on the property type, and the property that's there cannot be casted to that type since it returns a different property type. There is aSpecifyPropertyWeak
method that allows you to retrieve the property from an expression without returning the strongly typed object.
- Add type-converting filters on properties.
- Add option to create a new TComponent by default.
- Add support for lower frameworks: net45, net461, netcore
- Add attributes to more types
- Property getters/setters
- Assemblies
- Return types
- Add an attribute to all properties
- Index properties (and attributes)
- Add a configurable option: when adapter method is called and component is null,
enum NullBehavior { ThrowNullReferenceException, ReturnDefault }
? - Reuse same parameter in linq methods if applicable.
Benchmarking and code analysis tools. See, e.g., https://visualstudiomagazine.com/articles/2017/10/01/code-analysis.aspx
- Event handlers
- OnGetProperty
- OnSetProperty
- OnCallMethod
- OnAboutToGetProperty
- OnAboutToSetProperty
- OnAboutToCallMethod
- Events
- Static methods
- More complex linq expressions with a syntax that isn't awful.
- Add more Func and Action overloads.
- Add placeholder tests for 0.2.0 features.
- Generic methods (and attributes)
- ref and out parameters
- NAdapter.Strategies
- Custom constructors
- Attributes on constructors (and their parameters)
- Saving the types as an assembly which can be statically referenced at compile-time later.
NAdapter
could then be useful as a code-generation tool.