6
\$\begingroup\$

There are sometimes situations where you have a couple of related classes with static fields or properties but you cannot access them via T because you cannot define interfaces for static memebers.

I thought I write a helper that by utilizing dynamic allows me to do that anyway. It is implemented by the generic DuckObject<T> overriding the relevant APIs of its base type DynamicObject and throwing exceptions when a member isn't static or is an indexer.

public class DuckObject<T> : DynamicObject
{
    private static readonly DuckObject<T> Duck = new DuckObject<T>();

    public static TValue Quack<TValue>(Func<dynamic, dynamic> quack)
    {
        return (TValue)quack(Duck);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var member = typeof(T).GetMember(binder.Name).SingleOrDefault();
        switch (member?.MemberType)
        {
            case MemberTypes.Field:
                result = typeof(T).InvokeMember(binder.Name, BindingFlags.GetField, null, null, null);
                break;
            case MemberTypes.Property:
                result = typeof(T).InvokeMember(binder.Name, BindingFlags.GetProperty, null, null, null);
                break;
            default:
                throw new StaticMemberNotFoundException<T>(binder.Name);
        }
        return true;
    }

    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        throw new InvalidOperationException($"Cannot use an indexer on '{typeof(T)}' because static types do not have them.");
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var member = typeof(T).GetMember(binder.Name).SingleOrDefault();
        switch (member?.MemberType)
        {
            case MemberTypes.Method:
                result = typeof(T).InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, null, args);
                break;
            default:
                throw new StaticMemberNotFoundException<T>(binder.Name);
        }
        return true;
    }
}

In cases where I don't have T, I can use another helper that works with Type:

public class DuckObject
{
    private static readonly ConcurrentDictionary<Type, dynamic> Cache = new ConcurrentDictionary<Type, dynamic>();

    public static TValue Quack<TValue>(Type type, Func<dynamic, dynamic> quack)
    {
        var duck = Cache.GetOrAdd(type, t => Activator.CreateInstance(typeof(DuckObject<>).MakeGenericType(type)));
        return (TValue)quack(duck);
    }
}

Accessing non-static members causes the StaticMemberNotFoundException:

public class StaticMemberNotFoundException<T> : Exception
{
    public StaticMemberNotFoundException(string missingMemberName)
    : base($"Type '{typeof(T)}' does not contain static member '{missingMemberName}'") { }
}

Example-1 (test)

Assuming there are two classes sharing the same property and method:

public class Foo
{
    public static string Baz => "Baaz-1";

    public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

public class Bar
{
    public static string Baz => "Baaz-2";

    public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

I can use their members like that:

DuckObject.Quack<string>(typeof(Foo), duck => duck.Baz).Dump();
DuckObject.Quack<string>(typeof(Bar), duck => duck.Baz).Dump();
DuckObject<Foo>.Quack<string>(duck => duck.Baz).Dump();
DuckObject<Bar>.Quack<string>(duck => duck.Baz).Dump();
DuckObject<Bar>.Quack<string>(duck => duck.Print("test"));
DuckObject<Bar>.Quack<string>(duck => duck.Print(null));
DuckObject<Bar>.Quack<string>(duck => duck["Bum!"]); // throws

Example-2 (real)

I use it to access ValueMap of CronFields in my cron-expression parser. They initialize values valid for a specific field:

public class CronSecond : CronField
{
  public static readonly IReadOnlyDictionary<string, int> ValueMap;

  static CronSecond()
  {
      ValueMap =
          Enumerable
              .Range(0, 60)
              .ToDictionary(x => x.ToString(), x => x);
  }

  [...]
}

The parser uses them via T

internal static bool TryParse<T>(string input, out CronField cronField) where T : CronField
{
  [...]

  var fieldValues = DuckObject<T>.Quack<IReadOnlyDictionary<string, int>>(duck => duck.ValueMap);

  [...]
}

Do you think this is a good idea or do you see anything that could be improved?

\$\endgroup\$
  • \$\begingroup\$ Are you sure it works? It seems like you're passing the wrong type to the Func<dynamic, dynamic>. \$\endgroup\$ – Denis Apr 14 '18 at 14:43
  • \$\begingroup\$ @Denis of course it works ;-) Why shouldn't it? Everything's fine, it must be dynamic, this is the trick. \$\endgroup\$ – t3chb0t Apr 14 '18 at 14:45
  • \$\begingroup\$ Well DuckObject doesn't have a Buz property, does it? duck => duck.Baz, duck is DuckObject here. Or am I missing something? \$\endgroup\$ – Denis Apr 14 '18 at 14:46
  • \$\begingroup\$ @Denis you're right, it doesn't but it's a DynamicObject and the runtime calls the respecitve override and lets you pick that property/field/method - here by reflection from T. \$\endgroup\$ – t3chb0t Apr 14 '18 at 14:47
  • \$\begingroup\$ My bad, I had few exception settings checked, which caused it to throw twice before calling TryGetMember. \$\endgroup\$ – Denis Apr 14 '18 at 15:10
4
\$\begingroup\$

Type safety

Do you think this is a good idea or do you see anything that could be improved?

Language preference is obviously subjective. Many languages can achieve the same goal, albeit in a different way (usually). Every language has it's pro's and cons. Type safety is a big pro for C#.
Type safety comes with its own drawbacks. A type safe compiler is considerably more pedantic, as it needs to ascertain types at all times.

"Is it a good idea?" is a bit broad. It requires us to outweigh the benefit of having type safety versus the benefit of not having type safety; which inherently requires us to evaluate how important type safety is, and how needed your current fake duck typing solution is.

Secondly, this can devolve into a discussion as to how complex you want your future development to be, and how risky you want your runtime to be. These are all subjective boundaries; not every developer will see eye to eye here.

This is a source of endless discussion. An experienced Javascript developer will be used to having no type safety, and maybe won't think that type safety is all that necessary. A C# developer might instead strongly rely on type safety and will feel in uncharted territory when they abandon type safety, and therefore refuse to work without it.

To prevent this discussion, I'm going to draw a reasonable line of compromise here, for the sake of providing an answer which is not endlessly argumentative.

You're using C#, so I assume you want to leverage the benefits of C#. Type safety is desirable. When two solutions exist to the same problem, and only one of them is type safe; you must pick the type safe option.
Effectively, solutions without type safety are only valid in absence of type safe solutions.


Relying on compiler inference.

In a type safe language, relations need to be defined explicitly. That is the purpose of type safety: it only allows things it knows are correct. Without mentioning that Foo.Print() and Bar.Print() have something in common; the compiler will never guess that they have something in common just because they have the same signature.

For example:

public class NumberMultiplier
{
    public int Execute(int a, int b) {  return a * b; }
}

public class UserStateUpdater
{
    public void Execute(int userId, int stateId) { /* set User state in DB */ }
}

Should these classes be abstracted in a way that you can reusably call Execute on either class, since they have the same signature? I don't see a reason to.

Based on your expectation that the compiler sees the similarity between Foo.Print() and Bar.Print(), that means that the compiler will then also draw a similar parallel between NumberMultiplier.Execute() and UserStateUpdater.Execute().

This is leading to a system where you would define architectural structure via naming; which is wrong for many reasons. It inherently causes naming conflicts (avoiding particular names can lower readability as you'd have to invent apt synonyms which is not always feasible) and violates encapsulation (since the developer of NumberMultiplier and the developer of UserStateUpdater need to coordinate in order to prevent the compiler from making unwanted inferences.

Taking this further; imagine if either of these classes is part of a library that is used. The consumer of said library would effectively be disallowed from using a method with a similar signature, regardlesss of which namespace or class he's working it.
Imagine if both methods are part of two separate libraries that are being used. What then?


Relations need to be defined explicitly

Your expectation (and workaround to enable that expectation) feels very dirty. Ignoring static for a second; if you have two classes that have the same property/method and you wish to point out the relation between Foo.Print() and Bar.Print(), you need to express that relation, usually through inheritance or an interface.

You're correct that neither solution can be (directly) utilized for static methods and properties. However, that is a technical limitation, not an explicit philosophical decision.

The inability to declare a relationship is not an implicit statement by the language developers that you shouldn't express that relation. You're inferring that this is the case based on the absence of a possibility, which is essentially trying to prove a negative ("There is no car in my garage. Therefore, cars should not be stored in garages").

The issue here is that it's a clash of intentions:

  • Statics exist to provide global information that is supposed to be universally correct.
  • Having a value that is different for different classes inherently means that the value is not universally correct, since it's different on different levels.

The second bullet point effectively negates the need for a static.

I think you are confusing a static property and a reusable/related type.


The use case

public class Foo
{
    public static string Baz => "Baaz-1";

    public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

public class Bar
{
    public static string Baz => "Baaz-2";

    public static void Print(string message) => Console.WriteLine(message ?? Baz);
}

I genuinely can't think of a use case where this is both relevant and the better approach. Your second (real) example isn't helping me clarify this. I understand the code, but not why statics are the better approach here.

If you're expecting to resuably refer to static properties of types; that suggests that you already want to rely on a system where types are used dynamically (the same way values are), which is simply not what C# is built for (Delphi, on the other hand, has taken this approach to heart). While that doesn't mean it's impossible to implement it in C#, you're orthogonally going against the grain of C#. This creates a complication of the code base and a lot more work for developers; which you need to justify by showcasing what you stand to gain by doing so. I can't see a valid justification for this in your posted code or explanation.

Regardless, that can be a failure to understand the use case on my part. Let's assume that the use case for statics is valid.


A clearer alternative

You can implement this by separating the static property from the expressed type relation; by creating an additional (class) abstraction.

public class MyStaticThing
{
    public string Baz;
    public void Print(string message) => Console.WriteLine(message ?? Baz);
}

public class Foo
{
    public static MyStaticThing Static = new MyStaticThing()
                                            {    
                                                Baz = "Baaz-1"
                                            };
}

public class Bar
{
    public static MyStaticThing Static = new MyStaticThing()
                                            {    
                                                Baz = "Baaz-2"
                                            };
}
  • Foo still has its static property. Every Foo objects has the same MyStaticThing.
  • Foo.Static has different values from Bar.Static, but they both have the same properties/methods (enforced by type safety).
  • Implementing the MyStaticThing class has prevented you from violating DRY (your Print() methods were a clear copy/paste job).
  • You can handle both static property values in a reusable manner:
public void HandleMyStaticThing(MyStaticThing myThing)
{
    //....
}

Is your answer actually solving a problem?

I've been looking at it again and again, and I simply can't see the purpose of your duck typing. It seems to complicate the syntax without providing anything in return.

I noticed that you're not really handling your type in a reusable pattern. Every usage example explicitly lists the type you're expecting:

DuckObject.Quack(typeof(Foo), duck => duck.Baz).Dump();
DuckObject.Quack(typeof(Bar), duck => duck.Baz).Dump();
DuckObject<Foo>.Quack(duck => duck.Baz).Dump();
DuckObject<Bar>.Quack(duck => duck.Baz).Dump();
DuckObject<Bar>.Quack(duck => duck.Print("test"));
DuckObject<Bar>.Quack(duck => duck.Print(null));
DuckObject<Bar>.Quack(duck => duck["Bum!"]); // throws

This effectively negates what you're trying to do. Without implementing any change to your code, every line can already be rewritten to not use your extra implementation layer, while retaining the same functionality.

  • DuckObject.Quack<string>(typeof(Foo), duck => duck.Baz) (and similar) can be rewritten as Foo.Baz (and similar).
  • DuckObject<Foo>.Quack<string>(duck => duck.Baz) (and similar) can be rewritten as Foo.Baz (and similar).

There is one interesting difference:

DuckObject<Bar>.Quack<string>(duck => duck["Bum!"]);

This throws a runtime exception.

Bar["Bum!"];

This throws a (quite obvious) compile time exception. Furthermore, this also has nothing to do with the static properties of Bar, so I'm not sure why this usage is relevant in the current decision.

Having compile time exceptions is better than having runtime exceptions. A developer isn't required to run the application in order to find out if his code is syntactically valid.
Applying the stance I posted in the beginning, this means that the second option is preferred, as it retains type safety.


Additionally:

DuckObject<Bar>.Quack<string>(duck => duck.Baz);
DuckObject<Bar>.Quack<string>(duck => duck["Bum!"]);

Your intended usage imples that you think static and non-static properties should be addressed the same way.

This is simply not the case!

Bar myBar = new Bar() { Name = "Test" };

var nonStaticValue = myBar.Name;
var staticValue =    Bar.Baz;

Notice the difference between myBar.Name and Bar.Baz. One references the object, the other references the type. This is a fundamental difference.

The way you're trying to enforce that static and non-static properties are retrieved the same way, suggests to me that you misunderstand statics from a structural point of view. That leads me to wonder if you're actually using statics appropriately in your classes; which in turn raises questions as to whether your fake duck typing solution is based on nothing more than a design flaw.

This isn't meant as harsh as it possibly sounds over written text; but the more I consider your intentions, the more I'm getting convinced that your fake duck typing is the Y of the XY problem where X (using statics this way) is the real issue.

\$\endgroup\$
  • \$\begingroup\$ This is an amazing review! I'll need a couple of days to think about it ;-) \$\endgroup\$ – t3chb0t May 16 '18 at 12:25
  • \$\begingroup\$ @t3chb0t: Let me know what you conclude. Whenever I consider something as having no purpose, I get paranoid about whether I maybe missed an important point somewhere :) \$\endgroup\$ – Flater May 16 '18 at 12:30
  • 1
    \$\begingroup\$ I've read it a couple of times and I agree with your reasoning. Also the alternative example is much better than using static properties and my helper. I didn't want to instantiate the classes themselves only to get data that is not instance-related. I now admit that creating another data-class with the appropriate interface and in my case using <T> for retrieving them e.g. from a dictionary is cleaner. You got it all right. Many thanks ;-) \$\endgroup\$ – t3chb0t May 22 '18 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.