I found this thread: How to change the same properties of different objects in one method?
But I have an additional requirement: I don't necessarily have a common baseclass for the objects:
I wish to change "Font" attribute on more objects with a function, but these objects are not originating from a common baseclass.
I'm however sure, that they all have "Font" attribute (or in case not let's drop a compile-time error). How am I supposed to do this with .NET Core7 in C#?
private void RescaleFont<?>(? control_with_font_attribute, double scale) {
control_with_font_attribute.Font = ...;
CodePudding user response:
As @Selvin already implied in their comment:
Use the dynamic
keyword.
Objects of type dynamic bypass static type checking and it is assumed that it supports the operation or called attribute.
If there are any errors you would get an exception at runtime. However at compiletime everything will seem "fine".
Your method would look like the following:
private void RescaleFont(dynamic control_with_font_attribute, double scale)
{
control_with_font_attribute.Font = ...;
}
However please note that I'm not quite sure how safe it is to use or if it's acceptable designwise.
CodePudding user response:
There are several options:
The best one - introduce a common base class or interface:
public interface IHaveFont { FontType Font {get;set;}} private void RescaleFont<T>(T control_with_font_attribute, double scale) where T : IHaveFont { control_with_font_attribute.Font = ...; } // or private void RescaleFont(IHaveFont control_with_font_attribute, double scale) { control_with_font_attribute.Font = ...; }
Move the shared functionality to the common method and create bunch of overloads (you can generate them with source generators if needed):
private void RescaleFont(SomeFontTypeA control, double scale) { control.Font = Rescale(control.Font, scale)); } private void RescaleFont(SomeFontTypeB control, double scale) { control.Font = Rescale(control.Font, scale)); }
just use source generator to generate all required methods
use
dynamic
type (the other answer) - no type safety, runtime errors in case of property/field missing or type mismatch (can be mitigated with custom analyzer)use reflection - hence the
dynamic
but with ability to improve performance via "caching" it via dynamic compilation of expression trees (see this answer for some inspiration)// generic so later can cache the reflection private void RescaleFont<T>(T control, double scale) where T : IHaveFont { var pi = typeof(T).GetProperty("Font"); var newValue = pi.GetMethod.Invoke(control, null); pi.SetMethod.Invoke(control, new [] { newValue }); }
Use expression trees - a bit better type safety, but still possible to have runtime errors (if there is no setter for example) and need to duplicate the getter:
private void RescaleFont<T>(T control, Expression<Func<T, FontType>> fontGetter, double scale) { // analyze the fontGetter and use it to generate the setter } // and usage RescaleFont(someControl, x => x.Font, scale)
CodePudding user response:
A. Write it twice
Write the method twice. There should be minimal duplication needed:
private void RescaleFont(Type1 ctrl, double scale)
{
ctrl.Font = GetFont(scale));
}
private void RescaleFont(Type2 ctrl, double scale)
{
ctrl.Font = GetFont(scale));
}
Please note that only setting the font is duplicated and GetFont
is not duplicated, it's just called from two places.
B. Add an interface
private void RescaleFont(IWithFont ctrl, double scale)
{
ctrl.Font = GetFont(scale));
}
C. Control.Font Property
Are you sure that your controls aren't inheriting from the same base class, something like System.Windows.Forms.Control
?
private void RescaleFont(System.Windows.Forms.Control ctrl, double scale) { ctrl.Font = GetFont(scale)); }
D. Use reflection
using System.Linq.Expressions;
using System.Reflection;
class A
{
public Font Font { get; set; } = new Font("Arial", 4);
}
class B
{
public Font Font { get; set; } = new Font("Arial", 3);
}
class C
{
}
static void SetFont<T>(Font toSet, T target, Expression<Func<T, Font>> outExpr)
{
var expr = (MemberExpression)outExpr.Body;
var prop = (PropertyInfo)expr.Member;
prop.SetValue(target, toSet, null);
}
Then:
var exampleFont = new Font("Arial", 11);
var a = new A();
SetFont(exampleFont, a, x => x.Font);
var b = new B();
SetFont(exampleFont, b, x => x.Font);
var c = new C();
SetFont(fontX, c, x => x.Font); // error CS1061: 'Program.C' does not contain a definition for 'Font'