C
C#2y ago
skyslide22

❔ Python Decorators in C# ??? How do i intercept method calls with attributes?

Is is possible to intercept/prevent a method call in c# with attributes? i was thinking about something like python decorators, which can decide what happens with the method call, args and return value
[ThrowIfIdMatches("id", 69)]
public string MyMethod(int id)
{
return "my id is " + id
}
[ThrowIfIdMatches("id", 69)]
public string MyMethod(int id)
{
return "my id is " + id
}
that should throw a ValueError if the id parameter is 69, for example
45 Replies
Thinker
Thinker2y ago
You can't actually no you can but it takes the power of literal black magic Bottom line is that Python-like decorators just aren't supported in regular C#.
Jimmacle
Jimmacle2y ago
if you have the ability to add an attribute to the method you also have the ability to add code that does the same thing, no?
Sossenbinder
Sossenbinder2y ago
This is probably a task for source generators or dynamic codegen Not sure what python does to make this work
skyslide22
skyslide22OP2y ago
i got 100 database "middlewares" where i just want to add attributes to catch exceptions if env.IsDevelopment() for example
Thinker
Thinker2y ago
SGs can't do this because they can't modify source
Sossenbinder
Sossenbinder2y ago
Yeah that's true, they'd have to put a decorator around
Thinker
Thinker2y ago
Then you should probably do that using middleware/filters
Sossenbinder
Sossenbinder2y ago
So the code needs to be in a specific layout
Jimmacle
Jimmacle2y ago
this would be IL weaving if anything
Thinker
Thinker2y ago
yeah, black magic
Sossenbinder
Sossenbinder2y ago
Yeah, that's going to work for everything I'd put source gen a level lower on the magic scale at least, but it won't work everywhere
skyslide22
skyslide22OP2y ago
this is what we do at the moment
namespace Application.Logic
{
public class AppLogic
{
private readonly Database _database;
private readonly EmailSender _emailsender;
private readonly BlobStorage _blobStorage;
private readonly QMessageHandler _qMessageHandler;

//Inject all services that will be used by logic layer
public AppLogic(Database database, EmailSender emailsender, BlobStorage blobstorage, QMessageHandler qmessagehandler)
{
_database = database;
_emailsender = emailsender;
_blobStorage = blobstorage;
_qMessageHandler = qmessagehandler;
}

public IEnumerable<procVermieterSelectGridAll_Results> procVermieterSelectGridAll()
{
return _database.procVermieterSelectGridAll();
}
public procVermieterSelect_Results? procVermieterSelect(procVermieterSelect_InputParams procVermieterSelect_InputParams)
{
return _database.procVermieterSelect(procVermieterSelect_InputParams).FirstOrDefault();
}
public IEnumerable<procFranchisorAll_Results> procFranchisorAll()
{
return _database.procFranchisorAll();
}
public IEnumerable<proccboshopsall_Results> proccboshopsall()
{
return _database.proccboshopsall();
}
public IEnumerable<Rents_PaidSelectList_Results> Rents_PaidSelectList(Rents_PaidSelectList_InputParams Rents_PaidSelectList_InputParams)
{
return _database.Rents_PaidSelectList(Rents_PaidSelectList_InputParams);
}

[ThrowIfInDevelopmentElse(null)]
public async Task<int?> Rents_PaidInsert(Rents_PaidInsert_InputParams Rents_PaidInsert_InputParams)
{
return await _database.Rents_PaidInsert(Rents_PaidInsert_InputParams);
}
}
}
namespace Application.Logic
{
public class AppLogic
{
private readonly Database _database;
private readonly EmailSender _emailsender;
private readonly BlobStorage _blobStorage;
private readonly QMessageHandler _qMessageHandler;

//Inject all services that will be used by logic layer
public AppLogic(Database database, EmailSender emailsender, BlobStorage blobstorage, QMessageHandler qmessagehandler)
{
_database = database;
_emailsender = emailsender;
_blobStorage = blobstorage;
_qMessageHandler = qmessagehandler;
}

public IEnumerable<procVermieterSelectGridAll_Results> procVermieterSelectGridAll()
{
return _database.procVermieterSelectGridAll();
}
public procVermieterSelect_Results? procVermieterSelect(procVermieterSelect_InputParams procVermieterSelect_InputParams)
{
return _database.procVermieterSelect(procVermieterSelect_InputParams).FirstOrDefault();
}
public IEnumerable<procFranchisorAll_Results> procFranchisorAll()
{
return _database.procFranchisorAll();
}
public IEnumerable<proccboshopsall_Results> proccboshopsall()
{
return _database.proccboshopsall();
}
public IEnumerable<Rents_PaidSelectList_Results> Rents_PaidSelectList(Rents_PaidSelectList_InputParams Rents_PaidSelectList_InputParams)
{
return _database.Rents_PaidSelectList(Rents_PaidSelectList_InputParams);
}

[ThrowIfInDevelopmentElse(null)]
public async Task<int?> Rents_PaidInsert(Rents_PaidInsert_InputParams Rents_PaidInsert_InputParams)
{
return await _database.Rents_PaidInsert(Rents_PaidInsert_InputParams);
}
}
}
take a look at the last method
Thinker
Thinker2y ago
wtf are these type and parameter names kekw
skyslide22
skyslide22OP2y ago
we got a code generator ^^
Jimmacle
Jimmacle2y ago
make the code generator follow standard C# guidelines peepo
skyslide22
skyslide22OP2y ago
those are old database table names etc, can not change that
Thinker
Thinker2y ago
you use database table names for type names...?
skyslide22
skyslide22OP2y ago
for models, yes our code generator creates c# classes from mssql database tables based on stored procedures for insert, select, update, delete, selectList
Jimmacle
Jimmacle2y ago
i'm glad i just use efc
Mayor McCheese
Well you can do it without black magic, but it's extremely expensive performance wise Assuming black magic is il weaving
Thinker
Thinker2y ago
hmm, how would you do it otherwise? like... replace the method IL at runtime?
Jimmacle
Jimmacle2y ago
one of my old game projects made liberal use of runtime IL emission and monkey patching https://github.com/TorchAPI/Torch/tree/master/Torch/Managers/PatchManager it's basically harmony from before harmony added a bunch of features also i didn't write it so don't ask me questions about it when
Mayor McCheese
Some di frameworks support interception, with absurd performance costs
Thinker
Thinker2y ago
Would love to see how that is done
Sossenbinder
Sossenbinder2y ago
Doesn't that use something like Castle DynamicProxies?
Mayor McCheese
Yes And castle DP is a bit of a perf nightmare, couple that with transient DI, it can get nasty
skyslide22
skyslide22OP2y ago
i think this solves my problem
public async Task<TResult?> ThrowIfNotProduction<TResult, TArg>(Func<TArg, Task<TResult>> func, TArg arg)
{
try
{
return await func(arg);
}
catch
{
if(_env.IsDevelopment())
throw;
}
return default;
}
// and then
public async Task<int?> Rents_PaidInsert(Rents_PaidInsert_InputParams Rents_PaidInsert_InputParams)
{
return await ThrowIfNotProduction(_database.Rents_PaidInsert, Rents_PaidInsert_InputParams);
}
public async Task<TResult?> ThrowIfNotProduction<TResult, TArg>(Func<TArg, Task<TResult>> func, TArg arg)
{
try
{
return await func(arg);
}
catch
{
if(_env.IsDevelopment())
throw;
}
return default;
}
// and then
public async Task<int?> Rents_PaidInsert(Rents_PaidInsert_InputParams Rents_PaidInsert_InputParams)
{
return await ThrowIfNotProduction(_database.Rents_PaidInsert, Rents_PaidInsert_InputParams);
}
Mayor McCheese
I'm not remotely a fan You're going to swallow exceptions because it's prod??
Jimmacle
Jimmacle2y ago
sounds like a bad idea log them at minimum
skyslide22
skyslide22OP2y ago
i want to return null to the frontend if an exception is raised in prod
Mayor McCheese
So do that with an exception handler
skyslide22
skyslide22OP2y ago
default(T) is probably not the best solution
Mayor McCheese
Is this aspnet core?
skyslide22
skyslide22OP2y ago
blazor wasm api yes
Mayor McCheese
Do you mean you have an api machine in wasm? Or you have a server side api you call?
skyslide22
skyslide22OP2y ago
server side api
Mayor McCheese
I'd just use a global exception handler than try and muddy up the works
skyslide22
skyslide22OP2y ago
my goal is to get rid of try catch in every method, i was thinking about a wrapper which handles the exceptions and may return null on production, with logging or email sending
Mayor McCheese
Just use the global exception handler and only add the middleware in prod The handler will grab the exception and you decide how to handle it and pass back whatever you want You can write your own middleware too if you want Just don't make your code a hot mess of environment proxy wrappers everywhere.
Thinker
Thinker2y ago
kinda, yeah, but that's not implemented yet
Mayor McCheese
Which?
cap5lut
cap5lut2y ago
probably the interceptor proposal: https://github.com/dotnet/csharplang/issues/7009
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server