How to use a static utility method for property se

2019-08-05 09:45发布

问题:

I'm trying to achieve two-way binding between a DataGridView and a BindingList that provides data for the DGV. Some columns do not yet reflect changes in the underlying list and I think it's because I have not provided property setter(s) to notify of property changes. Rather than code the setter for the Rows property the same way I did for the Process property, I'm trying to get more "elegant" and I realize I am stuck....

I stumbled upon a very interesting writeup for a more elegant approach and I'm trying to implement the concepts of it (please see): http://www.gavaghan.org/blog/2007/07/17/use-inotifypropertychanged-with-bindinglist/

Here is the code from Mike's article I want to use (established as Utilities.cs in my CBMI.Common project):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace CBMI.Common
{
public static class Utilities
{
    public static bool Set<T>(object owner, string propName,
        ref T oldValue, T newValue, PropertyChangedEventHandler eventHandler)
    {
        // make sure the property name really exists
        if (owner.GetType().GetProperty(propName) == null)
        {
            throw new ArgumentException("No property named '" + propName + "' on " + owner.GetType().FullName);
        }
        if (!Equals(oldValue, newValue))  // we only raise an event if the value has changed
        {
            oldValue = newValue;
            if (eventHandler != null)
            {
                eventHandler(owner, new PropertyChangedEventArgs(propName));
            }
        }
    return true;    // Please NOTE: I had to add this statement to avoid compile error:
        // "not all code paths return a value".
    }
}
}

So, my FIRST QUESTION about this: The author did not have a return statement in his article and I added it which resolved the compiler error. I'm guessing the eventHandler executes and returns and that this was an author omission and this should return true as the method wants a bool return type. Is that correct assumption?

My 2nd QUESTION shows what a C# rookie I am when I try to use this helper method above. I have coded this class into a separate file called InputFileInfo.cs in the same project (and namespace) as the above:

    using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace CBMI.Common
{
    public class InputFileInfo : INotifyPropertyChanged
    {
    private bool processThisFile;
    public bool Process
    {
        get { return processThisFile; }
        set
        {
        processThisFile = value;
        this.NotifyPropertyChanged("Process");
        }
    }

    public string FileName { get; set; }

    private long rowsReturned;
    public long Rows
    {
        get { return rowsReturned; }
        set
        {
        Utilities.Set(this, "Rows", ref rowsReturned, value, PropertyChanged);
        }

    }
    public string Message { get; set; } 

    // constructor
    public InputFileInfo(string fName)
    {
        Process = true;
        FileName = fName;
        Rows = 0;
        Message = String.Empty;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
    }
}

The setter for the 2nd property in this class is where I try to use Mike's static method:

Utilities.Set(this, "Rows", ref rowsReturned, value, PropertyChanged);

If I remove Utilities.Set and just code it as follows:

Set(this, "Rows", ref rowsReturned, value, PropertyChanged);

..then I get the compiler complaining that "the name 'Set' does not exist in the current context".

I tried adding a using Utilities; directive but that did not fix the problem.

Finally, I do not understand the parameters: ref T oldValue, T newValue
nor the parameter called value where the Set method is invoked.

Can someone please help me over these multiple confusions about this code so I can use these more advanced ideas?

---- EDIT UPDATE ---- Two good answers helped me get this working. The "2nd question" in the original post above remains a bit elusive. Added comments for each requesting a "best practice" on how to package this so I can use the simple invoking syntax as in Mike's original article. That is, I'm seeking to invoke "helper" static methods by the method name only. I want to understand how to invoke like:

set
{
    Set(this, "Rows", ref rowsReturned, value, PropertyChanged);
}

instead of having to code as:

set
{
    Utilities.Set(this, "Rows", ref rowsReturned, value, PropertyChanged);
}

I got this working by coding Utilities.Set but I guess the question morphs a bit into - "Where do I put static methods and how to call them so I don't have to "qualify" them with the classname?" I would like to understand how to package generally useful "utility" type methods that don't require an instance of an object. In this case, the static method is called Set but I'd like to be able to add other static methods such as:

public static int HelpfulMethodXXXX(string s, int num)

I have a separately compiled DLL (Vstudio project) containing only class file(s). Ultimately, I'd like to think I could use this class in other applications.

Where is the best place to declare these sort of static methods such that they could be invoked as:

int i = HelpfulMethodXXXX("Sample", testNumber);

instead of:

int i = ContainingClassName.HelpfulMethodXXXX("Sample", testNumber);

回答1:

1: All non-void methods need to have explicit return statements.

2: CMBI.Common is the namespace. Utilities is the name of your class. Set() is a function of your class.

The call to Set() only makes sense in the context of the Utilities class. Set() is not part of the global namespace - as such, if you want to call Set() outside of the Utilities class, you have to specify that you want the Utilities.Set(), as opposed to SomethingElse.Set(). (Inside Utilities, the compiler understands that Set() refers to Utilities.Set()).

Using statements can only include namespaces (CMBI.Common), or specific classes inside namespaces, if you don't want every class in the namespace (CMBI.Common.Utilities). They cannot, however, turn a class function into a global function.

3: T refers to the name of the Generic type that this function operates on. http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx

Generics allow the same code to manipulate, say, a collection of integers, and a collection of strings, while enforcing compile-time type safety (The compiler will give an error, if you try to push an integer into a collection of strings.)

ref means that the parameter is passed as a reference - and that changes made to the parameter within the body of a function will propagate to the parameter's value in the context of the function's caller.



回答2:

  • It looks like the return type should be changed from bool to void since it doesn't appear to ever return different values.

  • Yes, Utilities.Set is the correct syntax. There is no analog to static imports in Java, for example, so you have to qualify it with a class. You could use extension methods, if you chose, and be able to invoke the method like this.Set(...) instead. To do that, just add the this keyword in front of the first parameter in your Set method:

 

public static bool Set<T>(this object owner, string propName,
    ref T oldValue, T newValue, PropertyChangedEventHandler eventHandler)
  • ref T oldValue means that you can pass it a variable of type T and it will write the old value into it (in other words, pass-by-reference for value types). This way you can find out what the oldValue was. (though it seems to me an out parameter would have made more sense.)

  • T newValue is the new value you are trying to set it to. If you are asking what T is, it is a generic type and acts as a placeholder for whta the type really is. It figures out which type automatically based on the type of the argument you pass to it. (If you pass it a string, T acts exactly as if you used string instead of T.)

  • value is a special keyword in C# that only has special meaning in the set accessor of a property you define. It's the value you are trying to assign to the property.