Search code examples
c#.netgenericsdelegatespredicate

how to put an object of your own class in the Predicate delegate


I am making a data processing class, the return from the getData method of which is carried out on the basis of the function passed to it using the delegate Predicate.The getData method is planned to be used by other classes as well, so I used the generic But I can't put an object of my own class in the delegate parameter

I wanted to pass the generalization to the delegate, but I don't know how to do it. I've also tried putting my class directly into a function, but that doesn't help either.

public class Program
{
    public static void Main()
    {
        var result1 = new MyDataProcessing().GetData<MyFile>("C:\\",MyFunc);
        var result2 = new MyDataProcessing().GetData<MyFile>("C:\\", MyFunc2);
    }  
    static bool MyFunc(MyFile file)=>file.FileInfo.Name.Contains("A");
    static bool MyFunc2(MyFile file) => file.FileInfo.Length>1000;
}
interface MyData
{
}
public class MyFile:MyData
{
    public MyFile(string name)
    {
        FileInfo = new FileInfo(name);
    }
   public FileInfo FileInfo { get; }
}

public  class MyDataProcessing 
{
    public List<T> GetData<T>(string path,Predicate<T>? filter = null)
    {
        List<T> list = new();
        IEnumerable<string> files;
        if (filter != null && typeof(T).Name is MyFile)
        {
            files = Directory.EnumerateFiles(path).Where(x => filter(new MyFile(x)));
            foreach (var file in files)
                list.Add(new MyFile(file));
        }
        return list;
    }
}

Thanks! I took your comments into consideration and that's what happened, are there any comments on this code?

    public class Program
{
    public bool MyFunc(IMyData data)
    {
        if (data is MyFile myfile)
        {
            return myfile.FileInfo.Name.Contains("A");
        }

        return false;
    }
    public bool MyFunc2(IMyData data)
    {
        if (data is MyFile myfile)
        {
            return myfile.FileInfo.Length > 1000;
        }

        return false;
    }

    public static void Main()
    {
        var p = new Program();
        var result1 = new MyDataProcessing().GetData("C:\\", p.MyFunc);
        var result2 = new MyDataProcessing().GetData("C:\\", p.MyFunc2);
    }


    public interface IMyData { }

    public class MyFile : IMyData
    {
        public MyFile(string name)
        {
            FileInfo = new FileInfo(name);
        }
        public FileInfo FileInfo { get; }
    }

    public class MyDataProcessing
    {
        public List<IMyData> GetData(string path, Predicate<IMyData>? filter = null)
        {
            List<IMyData> list = new();
            IEnumerable<string> files;

            if (filter != null)
            {
                files = Directory.EnumerateFiles(path).Where(x => filter(new MyFile(x)));
                foreach (var file in files)
                    list.Add(new MyFile(file));
            }
            return list;
        }
    }

Solution

  • I found a solution using the new Static abstract members in interfaces feature:

    public interface MyData<T>
    {
        T Data { get; }
        static abstract IEnumerable<T> EnumerateData(string path);
        static abstract MyData<T> Create(T info);
    }
    

    The interface contains a static method for enumerating the data and one for the creation of a MyData object with one parameter. This is useful since we cannot declare constructors in the interface.

    The idea is to delegate the decision of how to enumerate the input data and how to create a data class to the data class itself as static methods and remove it from the data processing class.

    An example implementation is:

    public class MyFile : MyData<FileInfo>
    {
        public MyFile(FileInfo fileInfo)
        {
            Data = fileInfo;
        }
    
        public FileInfo Data { get; }
    
        public static IEnumerable<FileInfo> EnumerateData(string path)
            => new DirectoryInfo(path).EnumerateFiles();
    
        public static MyData<FileInfo> Create(FileInfo info) => new MyFile(info);
    }
    

    We can declare a data processing class like this (note that I made the method static):

    public class MyDataProcessing
    {
        public static List<U> GetData<T, U>(string path, Func<T, bool>? filter = null)
            where U : MyData<T>
        {
            var data = U.EnumerateData(path);
            if (filter != null) {
                data = data.Where(filter);
            }
            return data.Select(d => (U)U.Create(d)).ToList();
        }
    }
    

    I was struggling with a strange error in the Select method until I found that the solution was to cast the MyData<T> created by U.Create(d) to U:

    Error CS8920 The interface 'MyProject.MyData' cannot be used as type argument. Static member 'MyProject.MyData.EnumerateData(string)' does not have a most specific implementation in the interface.

    Possible usage:

    List<MyFile> result =
        MyDataProcessing.GetData<FileInfo, MyFile>(@"C:\", f => f.Length > 1000);