Home > Software design >  Change the same property of different objects without a common baseclass?
Change the same property of different objects without a common baseclass?

Time:01-26

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'
  • Related