3

Is it possible to dynamically create static fields in a class?

class Pages
{
    private static T getPages<T>() where T : new()
    {
        var page = new T();
        PageFactory.InitElements(Browsers.getDriver, page);
        return page;
    }
    public static HomePage Home => getPages<HomePage>();
    public static DashboardPage Dashboard => getPages<DashboardPage>();
    public static ProfilePage Profile => getPages<ProfilePage>();
}

Right now I'm adding them one by one myself. Is there a way to dynamically add them? HomePage, DashboardPage and ProfilePage live under MyProject.Pages namespace, if I know how to dynamically create the static fields, I could loop through that namespace and dynamically create them.

Drago
  • 1,755
  • 3
  • 19
  • 30
  • how often you do this? and how many pages we are talking about? – Maytham Fahmi Oct 08 '19 at 09:08
  • I have over 70 pages. The pages class is to make my life easier in the tests so I can refer to a page `Pages.HomePage.Login()`. Right now each time I create a new page I'm adding it manually to the `Pages` class. – Drago Oct 08 '19 at 09:10
  • 7
    Is that much easier than `Pages.GetPage().Login()`? That wouldn't require anything dynamic, as far as I can see. (I'd definitely call it `GetPage` rather than `GetPages`, as it's only creating a single page...) – Jon Skeet Oct 08 '19 at 09:15
  • Are you asking about code generation? Or are you expecting something that happens at runtime? I am not sure what you are asking for. – John Wu Oct 08 '19 at 09:30
  • @JohnWu I was looking for something that happens at runtime. I like Jon Skeet's idea and will use this. However I'm curious to see if it's possible in C# to dynamically create static fields at runtime and to see how it's done. – Drago Oct 08 '19 at 09:33
  • 1
    It is not possible to create static fields at runtime (`dynamic` aside). After all, you're writing tests which access those fields at compile-time - if those fields were only available at runtime, this would not work. – canton7 Oct 08 '19 at 09:36
  • 2
    Jon's answer is the way to go. Not only because it solves your issue, but because you really [ought to be using a method, not a property](https://stackoverflow.com/questions/2101646/is-object-creation-in-getters-bad-practice). – John Wu Oct 08 '19 at 09:41

2 Answers2

4

What you ask isn't possible, as compiled code depends on the source it was compiled from. So unless you generate your sources dynamically (you could research T4 or T5 for that), a compiled class can not be extended in the way you describe.

However with a slightly different setup, the need for all this might become small or even absent. By making class Pages static, and then using it as follows: using static <namespace>.Pages, you can use its methods without prefixing them with the class name.

Full example (and accompanying .NET Fiddle):

using System;
using Project;
using static Project.Pages;

public class Program
{
    public static void Main()
    {
        var home = GetPage<Home>();
        var dashboard = GetPage<Dashboard>();
        var profile = GetPage<Profile>();

        Console.WriteLine(home.GetType().Name);
        Console.WriteLine(dashboard.GetType().Name);
        Console.WriteLine(profile.GetType().Name);
    }
}

namespace Project
{
    public static class Pages
    {
        public static T GetPage<T>() where T : new()
        {
            var page = new T();
            // ...
            return page;
        }
    }

    public class Home { /* ... */ }

    public class Dashboard { /* ... */ }

    public class Profile { /* ... */ }
}

You can then make it more compile-safe by making all Page classes implement an IPage interface, and adding a where T: IPage constraint to GetPage<T>().

Peter B
  • 22,460
  • 5
  • 32
  • 69
-1

Rather than adding properties, i would probably be easier if all those pages inherit from a common base class, and make a dictionary with a string as key. this way, you could easily add pages dynamically, without having to rely on generics.

public static pages = new Dictionary<string, IPage>{
   {"homepage", GetPages<HomePage>()},
   ...
}

or something like that, you probably don't even need a dictionary, you could do it with a list.

Even better would be to use an IOC container, and add a servicecollection extension to register all types that inherit from IPage. that way, you could just inject those pages wherever you need them, and it will register them automatically.

Glenn van Acker
  • 317
  • 1
  • 14
  • 2
    1) This caches a single HomePage, whereas the OP's question creates a new HomePage per access (which is presumably important). 2) Relying on strings is less safe (and IMO significantly less desirable) then doing either `Pages.Home` or `Pages.GetPage()` – canton7 Oct 08 '19 at 09:22
  • You're probably right about the strings, that's why i also said it may be interesting to just use a list. however, The ioc approach is very common, and could work out great if you register a module, that in turn registers it's pages in the container. that way, you don't even need to call getPage, you could just inject it. – Glenn van Acker Oct 08 '19 at 10:36