1

The current structure of my classes goes a little bit like this: PC.Processor.Architecture[0] = The Architecture of the first processor (assuming a multi-processor system).

How I ideally want it is more like this: PC.Processor[0].Architecture, because it's a bit more self explanatory this way.

Is there a fairly efficient way of doing this with what I have? Bear in mind there are like, over 9000 properties in each of the classes Processor, Motherboard, Memory etc and WMI calls are not CPU-cheap to run.

Here are the important snippets for my classes

class PC
{
    public Processor Processor;
    public Motherboard Motherboard;

    // Constructor
    public PC()
    {
        Processor = new Processor();
        Motherboard = new Motherboard();
    }

    // Method to get all info sequentially
    public void GetAllInfo()
    {
        Processor.GetInfo();
        Motherboard.GetInfo();
    }
}

class Processor
{
    public string[] Architecture;
    public string[] Availability;
    public UInt16[] Cores;

    public void GetInfo()
    {
        // Get WMI Information from custom process
        // Returns as an array of ManagementObjects for each matched device (which is a bit like an array of dictionaries)
        ManagementObject[] WMIData = DataRetriever.GetWMIData("Win32_Processor");
        try
        {
            for (int i = 1; i < WMIData.Length; i++)
            {
                this.Architecture[i] = (string)WMIData[i]["Architecture"];
                this.Availability[i] = (string)WMIData[i]["Availability"];
                this.Cores[i] = (UInt16)WMIData[i]["NumberOfCores"];
            }
        }
        catch (NullReferenceException e)
        {
            // To be implemented
        }
    }
}

FURTHERMORE
There may be more than one WMI search query per class. For example, HardDrive needs to make use of both Win32_PhysicalMedia and ATAPI_SmartData (or whatever the class actually is.)

Sam
  • 86,580
  • 20
  • 181
  • 179
Chris Watts
  • 6,197
  • 7
  • 49
  • 98
  • 1
    So one instance of the Processor class is used to represent N processors? That seems like a design problem. Care to explain the reasoning for that? Why not have Processor represent a single processor? – Servy Mar 06 '12 at 21:21
  • 2
    @sll, what are you talking about? First of all, List is backed by an array. Second of all, boxing/unboxing doesn't occur with reference types. – Chris Shain Mar 06 '12 at 21:22
  • Your PC class should have a collection of Processor instances, perhaps a member called Processors (with the s). Populate your collection of processors within PC. This way you can achieve what you want: PC.Processors[0].Cores – Tung Mar 06 '12 at 21:31
  • @servy This is a program to retrieve information about the computer it runs on. Currently, yes, one instance does list all physical processors. How I want it is an array of Processor instances each describe one processor. – Chris Watts Mar 06 '12 at 21:34
  • @Tung I did think of doing that, but that would make the PC class massive and make obsolete the purpose of this segmented design. – Chris Watts Mar 06 '12 at 21:35
  • @CJxD, in the end you will have a collection of processors. You need to ask yourself if it makes more sense for a PC to have many processors or a processor to have many processors. The fact that you gravitated towards PC.Processor[0] indicates to me that you would want the former;) – Tung Mar 06 '12 at 22:01
  • @ChrisShain : thanks for the corection, I've mixed up entirely all – sll Mar 07 '12 at 14:03

3 Answers3

2

You should probably be making these calls lazily, e.g.:

class PC
{
    public IEnumerable<Processor> Processors {get; private set;}

    // Constructor
    public PC()
    {
        Processors = new List<Processor>();
        for (var i = 0; i < GetProcessorCount(); i++)
            Processors.Add(new Processor(i));
    }
}

class Processor
{
    public int ProcessorIndex { get; private set; }

    private String _architecture;
    public string Architecture { 
        get { 
            // Return architecture if it's already been retrieved, 
            // otherwise retrieve it, store it, and return it.
            return _architecture ?? (_architecture == getArchitecture(ProcessorIndex));
        }
    }
    public Processor(int processorIndex) {
        ProcessorIndex = processorIndex;
    }
}

This way you get the semantics you want, e.g.

Console.Out.WriteLine(myPCInstance.Processors[0].Architecture);

and at the same time you are only retrieving that information when it is actually asked for.

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • I may sound like an idiot, but using a get{} call for each and every property would result in unreadably massive code. Is this adaptable by just assigning values to properties? – Chris Watts Mar 06 '12 at 21:46
  • Why would it be unreadable? You are going to need thousands of *something* for thousands of properties. Proper use of further nested classes, alphabetical order and perhaps regions should make this bearable, but short of sticking everything into a dictionary and using strings to get at it you are just going to have a lot of code here for thousands of properties. – Chris Shain Mar 06 '12 at 21:52
  • 500 properties x 1 line each = 500 lines of code to look through; 500 properties x 5 lines each = 2500 lines of code to look through. – Chris Watts Mar 06 '12 at 21:57
  • As opposed to *what*? I am confused by what alternative you could possibly be looking for. – Chris Shain Mar 06 '12 at 21:58
  • Why use lazy gets for each property? I don't understand. All I see it does it take up more paper when I print this all out for my coursework moderator to mark. The processor intensive bit is the GetWMIData function. – Chris Watts Mar 06 '12 at 22:05
  • Ah, this is homework? You should have flagged it as such. Also, you did not state that you needed help in **minimizing the amount of paper when you print this out, nor did you mention that minimizing CPU utilization was a goal.** You just asked how to achieve a slight change in syntax efficiently, which this does. – Chris Shain Mar 06 '12 at 22:09
  • Yeah, so basically this is a self-chosen project that eventually has to be marked. I'm not exactly the world's best expert in this language but I want to include reasonably advanced features without making the code-length too massive. Anyway, I'll use this answer and the other answer to find exactly what works. – Chris Watts Mar 06 '12 at 22:16
1

why not make a Processor class just have single properties Architecture, Cores etc and take a ManagementObject instance in its constructor? you can then pull the required data from the management object in the constructor of the Processor and just create many Processors in your PC object.

class PC
{
    //I'd encapsulate these in a property rather than a public field
    public Processor[] Processors;
    public Motherboard Motherboard;

    // Constructor
    public PC()
    {

        Motherboard = new Motherboard();
    }

    // Method to get all info sequentially
    public void GetAllInfo()
    {
        ManagementObject[] WMIData = DataRetriever.GetWMIData("Win32_Processor");
        Processors = new Processor[WMIData.Length-1];
        for (int i = 1; i < WMIData.Length; i++)
            {
            Processors[i-1] = new Processor(WMIData[i-1]); //assuming 0 based                
            }

        Motherboard.GetInfo();
    }
}

class Processor
{
    public string Architecture;
    public string Availability;
    public UInt16 Cores;

    public Processor(ManagementObject WMIData)
    {
        this.Architecture = (string)WMIData["Architecture"];
        this.Availability = (string)WMIData["Availability"];
        this.Cores = (UInt16)WMIData["NumberOfCores"];
    }
}

if you are worried about performance then you should hide your public fields behind properties and then make the calls lazily as you need them. Obviously this is a trade off between loading the data when you need it (which might involve delays at the time of access) or preloading all of it, (which might mean a delay at the start but will be quick when you need it). The desired result is down to whether you always needs all the data and how it is going to be used.

you could store the WMIData object in the Processor class and just read the values when you access the properties. It depends on where the slow bits are:

class Processor
{ 
    private ManagementObject WMIData;
    // obviously you might want to cache this value once it has been retrieved once
    public string Architecture{get{return (string)WMIData["Architecture"];}}
    public string Availability {get{return (string)WMIData["Availability"];}}
    public UInt16 Cores{get{return (UInt16)WMIData["NumberOfCores"]}}

    public Processor(ManagementObject WMIData)
    {
        this.WMIData = WMIData;
    }
}

EDIT

if you need more than 1 WMI query, then either pass the results of each WMI call to the object so it can get the data from them (sounds like it'll be slow) or give it just enough data so it can make those calls when it needs to:

class HardDrive
{
   private int index;
   private ManagmentObject physicalMediaInfo;
   private ManagementObject smartDataInfo;

   // choose one of these constructors.  this one lets you delay all the WMI calls till you need to do them 
   public HardDrive(int index)
   {
       this.index=index;
   }

   //this one means you have to make the calls in advance
   public HardDrive(ManagmentObject physicalMediaInfo,ManagementObject smartDataInfo)
   {
       this.physicalMediaInfo=physicalMediaInfo;
       this.smartDataInfo=smartDataInfo;
   }

   private ManagementObject PhysicalMediaInfo
   {
       get
       { 
          if(physicalMediaInfo==null)
          {
              ManagementObject[] WMIData = DataRetriever.GetWMIData("Win32_PhysicalMedia");
              physicalMediaInfo=WMIData[index];
          } 
          return physicalMediaInfo;         
       }
   }

   private ManagementObject SmartDataInfo
   {
       get
       { 
          if(smartDataInfo==null)
          {
              ManagementObject[] WMIData = DataRetriever.GetWMIData("ATAPI_SmartData");
              smartDataInfo=WMIData[index];
          } 
          return smartDataInfo;         
       }
   }

   //property for getting the details of the hard disk
   //uses the private property to ensure that the management object for the  is only loaded when its needed
   public int Sectors{get{return (int)PhysicalMediaInfo["Sectors"]};};

   //Same for the smart data.  
   public int SomeSmartData{get{return (int)SmartDataInfo["SomeSmartData"]};};

}

this approach allows you to only do each api call as a property from it is needed, and ensures that it is only done once regardless of how many properties from it are used. you could combine the constructors and allow the management objects passed in to be null, then you could provide the index which would be used to look up and ManagementObject instances which were not passed in with the constructor, but leave you free to pass in some ManagementObjects which could be preloaded if they were available or cheap...

EDIT 2

As for dealing with a refresh, assuming that refresh needs to ensure that next time data is accessed the latest info from the APi is requested you should be able to simply do this:

 public void Refresh()
 {
     this.physicalMediaInfo=null;
     this.smartDataInfo=null;
 }

This will then mean that next time Sectors is called the "Sectors" value will be re-queried from the WMIData as the existing WMIObject will be replaced.

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
  • Cores and Availability may not need to be arrays. I'm assuming cores is the number of cores a processor has, and availability is the availability of the processor – Tung Mar 06 '12 at 21:38
  • Issue with this idea is I cant necessarily complete Processor information with just one WMI query (as updated in the additional info). I might need several pieces of query data to successfully construct what I need. That's when performance starts dropping. Unless there's a way of merging ManagementObjects? – Chris Watts Mar 06 '12 at 21:50
  • 1
    the principle is the same though, either get and pass all necessary data the object needs to the object when it is constructed, or ensure it has enough information to get it itself as and when it needs is. I'll add an update... – Sam Holder Mar 06 '12 at 21:55
  • The edit is a much more realistic approach to the problem. It's a little bit bulky in my eyes, so once I implement this I'll share what I deem to be best for the structure but thanks anyway! – Chris Watts Mar 06 '12 at 22:18
  • This code is getting more and more unmaintainable as I do this o.o I now have to worry about how it handles when a Refresh() command is invoked. – Chris Watts Mar 06 '12 at 22:40
  • @CJxD presumably a refresh command just needs to make sure that next time a property is accessed it reinvokes the API method to get the data. Assuming this is the case a refresh simply sets all the ManagementObjects held in the fields to null. This will ensure that next time a property is accessed the ManagementObject will be requeried and so the data will be refreshed. I added an example. – Sam Holder Mar 07 '12 at 07:16
0

Thanks to everyone who's responded. I've come up with a reasonably elegant solution that suits my needs.

PC Class Example:

public class PC
{
    public List<Processor> Processor;

    // Constructor
    public PC()
    {
        this.Processor = new List<Processor>();
    }

    // Method to get all info sequentially
    public void GetAllInfo()
    {
        // These temporary stores fetch WMI data as ManagementObjects
        // Most cases will only need one WMI class.
        ManagementObject[] WMIDataTemp1;
        ManagementObject[] WMIDataTemp2;

        WMIDataTemp1 = DataRetriever.GetWMIData("Win32_Processor");
        foreach (ManagementObject Object in WMIDataTemp1)
        {
            this.Processor.Add(new Processor(Object));
        }
    }

    public void RefreshAll()
    {
        // Delete the lists and start again
        // Another option would be to foreach through the list elements and initialise each object again.
        this.Processor.Clear();
        GetAllInfo();
    }

    public void RefreshVolatileData()
    {
        // Extra function that will do some cool stuff later.
    }
}

Processor Class Example:

public class Processor
{
    // Define properties
    public string Architecture = "N/A";
    public string Availability = "N/A";
    public UInt32 CacheL2 = 0;
    public UInt32 CacheL3 = 0;

    // Overloaded constructor method
    // The one with no arguments does nothing to initialise the class
    // The one with the ManagementObject argument will call GetInfo to arrange the held data into the properties above
    public Processor() { }
    public Processor(ManagementObject wmiProcessor)
    {
        this.GetInfo(wmiProcessor);
    }

    // The main information handler for the classes.
    // This splits out the data in the ManagementObject into the class's own properties
    public void GetInfo(ManagementObject wmiProcessor)
    {
        // If anything fails, the try loop will just end without making a fuss
        // Because of the default values, N/A will be displayed everywhere if something fails here.
        try
        {
            this.Architecture = (string)wmiProcessor["Architecture"];
            this.Availability = (string)wmiProcessor["Availability"];
            this.CacheL2 = (UInt32)wmiProcessor["L2CacheSize"];
            this.CacheL3 = (UInt32)wmiProcessor["L3CacheSize"];
        }
        catch (Exception e)
        {

        }
    }
}

Usage example:

public PC Computer = new PC();
Computer.GetAllInfo();
textbox1.Text = Computer.Processor[0].Architecture

In the event a device needs to query more than one WMI class, each additional class can be listed as an extra parameter in the device constructor and GetInfo() method.

Chris Watts
  • 6,197
  • 7
  • 49
  • 98