Как сделать простой динамический прокси в C#
Я хочу построить динамический прокси-объект, чтобы добавить определенную функциональность к объекту.
в основном я хочу получить объект, обернуть его объектом, который выглядит идентично оригиналу, который я получил, и перехватить все вызовы.
class Wrapper : DynamicProxy// dynamic proxy is not a reall class, but i guess something like this exists...
{
public static T Wrap(T obj)
{
return (T) new Wrapper(obj);
}
public override object InterceptCall(MethodInfo info, object[] args)
{
// do stuff
}
}
Просто чтобы уточнить, я хочу сделать что-то похожее на фабрику каналов WCF...
Я добавляю вознаграждение, потому что мне нужен хороший способ для прокси-классов (не интерфейсов) и для обработки не виртуальных методов (как если бы я унаследовал и добавил метонд под ключевым словом "new"). Я уверен, что все это очень возможно, так как.Net делает это.
10 ответов
Я должен был написать это раньше, но не бери в голову.
В моей проблеме была особая "ошибка", которая мне нужна, чтобы иметь возможность прокси-классов, а не интерфейсов.
Есть два решения этого:
Реальный прокси и друзья, в основном, подразумевают использование.Net Remoting. Требуется один для наследования от ContextBoundObject.
Построение прокси с использованием System.Reflection.Emit, как это было сделано весной, вы также можете посмотреть на код их ProxyFactoryObject. Вот еще три статьи на эту тему.
Кстати, у второго подхода есть существенное ограничение, вы не можете использовать не виртуальные прокси.
Вы можете сделать это с помощью комбинации DynamicObject и ImpromptuInterface, но вам потребуется интерфейс, реализующий функции и свойства, которые вы хотите использовать для прокси.
public interface IDoStuff
{
void Foo();
}
public class Wrapper<T> : DynamicObject
{
private readonly T _wrappedObject;
public static T1 Wrap<T1>(T obj) where T1 : class
{
if (!typeof(T1).IsInterface)
throw new ArgumentException("T1 must be an Interface");
return new Wrapper<T>(obj).ActLike<T1>();
}
//you can make the contructor private so you are forced to use the Wrap method.
private Wrapper(T obj)
{
_wrappedObject = obj;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
try
{
//do stuff here
//call _wrappedObject object
result = _wrappedObject.GetType().GetMethod(binder.Name).Invoke(_wrappedObject, args);
return true;
}
catch
{
result = null;
return false;
}
}
}
Вы можете, конечно, решить потерять безопасность типов и пойти с DynamicObject, как я показал, а затем отбросить кастинг.
Я сделал прозрачную расширяемую версию этого прокси-объекта и открыл ее здесь.
В дополнение к Castle.DynamicProxy на Github есть также LinFu.DynamicProxy.
.NET 6.0 добавил нового кандидата в
Reflection
пространство имен: DispatchProxy . Команда объявляет об этом здесь . Пример использования содержится в статье.
Посмотрите на PostSharp. Я не знаю, как сделать то, что вы хотите, в vanilla .Net, но PostSharp предлагает такие вещи, как "OnMethodBoundaryAspect", которые можно использовать для замены или переноса кода внутри метода.
Я использовал его для ведения журналов, проверки параметров, обработки исключений и т. Д.
Существует бесплатная версия Community Edition, которая должна работать на вас. Он понадобится вам на вашем компьютере для разработки, а также на любом используемом вами сервере сборки.
Вот простой пример создания динамического прокси с использованием C# Emit
Вы также можете использовать AOP-фреймворк, такой как PostSharp
Другой вариант ContextBoundObject
,
В CodeProject была статья 8-9 лет назад, в которой этот подход использовался для отслеживания вызовов методов.
Для добавления любой функциональности до и после каждой функции в классе, Real proxy является хорошим подходом.
Так что теперь в T может быть любой TestClass. Создайте такой экземпляр для TestClass-
var _instance = (object) DynamicProxy (TestClass).GetTransparentProxy ();
Код для динамического прокси
class DynamicProxy<T> : RealProxy
{
readonly T decorated;
public DynamicProxy(T decorated) : base(typeof(T))
{
this.decorated = decorated;
}
public override IMessage Invoke(IMessage msg)
{
var methodCall = msg as IMethodCallMessage;
var methodInfo = methodCall.MethodBase as MethodInfo;
string fullMethodName = $"{methodInfo.DeclaringType.Name}.{methodCall.MethodName}";
try
{
var result = methodInfo.Invoke(decorated, methodCall.InArgs);
return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
return new ReturnMessage(e, methodCall);
}
finally
{
}
}
}
Вы можете сделать это, используя только DynamicObject из пространства имен System.Danymic, без использования каких-либо сторонних библиотек.
Пример кода:
public class DynamicProxy: DynamicObject
{
private readonly T _object;
// The inner dictionary.
Dictionary<string, object> dictionary = new Dictionary<string, object>();
// Getting a property.
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return dictionary.TryGetValue(binder.Name, out result);
}
// Setting a property.
// You can set up access control eg. if you don't want to
// set certain field, you can return false before putting
// the value into the inner dictionary
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (binder.Name.Equals("Rating")) return false;
dictionary[binder.Name] = value;
return true;
}
public DynamicProxy(T object)
{
_object = object;
dictionary["Name"] = object.GetName();
dictionary["Gender"] = object.GetGender();
dictionary["Interests"] = object.GetInterests();
dictionary["Rating"] = object.GetGeekRating();
}
public string GetName()
{
return (string)dictionary["Name"];
}
public int GetGeekRating()
{
return (int)dictionary["Rating"];
}
}
Затем в классе драйвера:
dynamic dynamicProxy = new DynamicProxy(person);
Таким образом, вы можете установить и получить поля с контролем доступа.
Вы не можете перехватить все вызовы статических, а не виртуальных или закрытых членов, если вы не заставите CLR подключаться к каждому вызову каждого метода / свойства для этого объекта и перенаправлять вызов на созданный вами поддельный. Этого можно добиться с помощью API.NET Profiler. TypeMock Isolator, например, использует его для мониторинга выполнения приложения, и когда метод вызывается, CLR уведомляет изолятор typemock, который позволяет Isolator полностью переопределить исходный класс.